Merge "Remove frameworks/base/apex/appsearch and associated tests" into stage-aosp-master am: 8efe26afd6

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15941404

Change-Id: I58bd9a43eee9e18586c753d560f599f9a8e6c93a
This commit is contained in:
TreeHugger Robot
2021-09-30 04:45:52 +00:00
committed by Automerger Merge Worker
130 changed files with 0 additions and 29151 deletions

View File

@@ -1,75 +0,0 @@
// Copyright (C) 2019 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package {
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_license"],
}
apex {
name: "com.android.appsearch",
manifest: "apex_manifest.json",
bootclasspath_fragments: ["com.android.appsearch-bootclasspath-fragment"],
systemserverclasspath_fragments: ["com.android.appsearch-systemserverclasspath-fragment"],
key: "com.android.appsearch.key",
certificate: ":com.android.appsearch.certificate",
updatable: false,
jni_libs: ["libicing"],
generate_hashtree: false,
}
apex_key {
name: "com.android.appsearch.key",
public_key: "com.android.appsearch.avbpubkey",
private_key: "com.android.appsearch.pem",
}
android_app_certificate {
name: "com.android.appsearch.certificate",
// This will use com.android.appsearch.x509.pem (the cert) and
// com.android.appsearch.pk8 (the private key)
certificate: "com.android.appsearch",
}
// Encapsulate the contributions made by the com.android.appsearch to the bootclasspath.
bootclasspath_fragment {
name: "com.android.appsearch-bootclasspath-fragment",
contents: ["framework-appsearch"],
apex_available: ["com.android.appsearch"],
// The bootclasspath_fragments that provide APIs on which this depends.
fragments: [
{
apex: "com.android.art",
module: "art-bootclasspath-fragment",
},
],
// Additional stubs libraries that this fragment's contents use which are
// not provided by another bootclasspath_fragment.
additional_stubs: [
"android-non-updatable",
],
}
// Encapsulate the contributions made by the com.android.appsearch to the systemserverclasspath.
systemserverclasspath_fragment {
name: "com.android.appsearch-systemserverclasspath-fragment",
contents: ["service-appsearch"],
apex_available: ["com.android.appsearch"],
}

View File

@@ -1,3 +0,0 @@
adorokhine@google.com
sudheersai@google.com
yamasani@google.com

View File

@@ -1,4 +0,0 @@
{
"name": "com.android.appsearch",
"version": 300000000
}

View File

@@ -1,51 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIJKQIBAAKCAgEAro9f/jvoIsj6ywuRmuUQS8UtprhohJitrovDMfm/T2R/WQvy
AvUxgetyF4XvBPCDRqCsGxXCJMQOn1furrAeTmWbGHPhA0PI1Ys/qtfNMbh9THyn
70I2c4X70CUQ+8/Y8BJ8CAB4iER/s9QtD28QLvM2BBUzRoKUSBGUYNMlYobjgRdK
57V7yg48LkvUIg1fzIW3M5gCgOXa0u1xOadKX3m7tzCboHcXp5anfWX5PH1+okRu
jzdI8OjtUq23qhoRw5Skz0Vbf4a+8t3kT3slF/Q7O8LoRPwpZsvIcvTyCGAqlra7
2L2LN4H1p+u2ko3r/QmRbJn2eXW07elkyrggXMyn2rTxibQgk53wYfSavMyNd/E7
+de/uJ60l2aPa+5KUaR8eYwchXEELdqQ+zRgSZ2711xCaY4glEj7DT6VlEEdr26x
akX0ra7e2sVGv1um/dvSyVO5aFKKjVvo4LqhWKWO8yvDMxmDDTNatvWhY2Bhd3RA
0hilYpWQFb9Tv5f4E0tZmfvlddgux7sw++Y/RIimBFoSyf5AezAUIFYYoYvEzytB
muq1/ecNHr+Z2tZMxN88sJVhzRzD9tKUyXhvxOV2Lg9TIeVTWGwQqgSnHWtIe+1p
cw8inPfYEhP4Q+3W/RlPvNdu75x8Nj2aG7bxZnhoQDRDw5ddgma27I+a8esCAwEA
AQKCAgBsNh9I6HRAVBz8kCBkSEnw3rwtFTZdtJQ+lw+bRHpvShqT5g7R/JQDOSTS
JkoE4uBOgT4P0E45Inz6FLW2/yDacqxR3UwJDRVMI/WFACCJCRhLuR8V+BLvTIjN
AJ1lrPSL5rmS8E/IEcakgQyp+6ypnkXHBCl0NXCcuKEl4N7VFE+mb/0UZPHnUSnH
fWR085uGmwH17u7mXxdnGKDPH8DALSPMLUrcj9dPIdqUpwl5kUZWa1uqVphWF98/
GMe5oE2Q0+3TO+i7xplKz3lAOFPHZLTvmCUK1tMHkZ6ifOwpewwLwB30/5N1BpB1
126nrWk0xKCtFUixBOHzdnLwJHKSbi7chQU5q39oAJoTfxdmAJlaG0zQHUQZ44MQ
gemzSA7uJbtoAOAZVF1K14xbIpnfidqTB7N3RCmiJE+/Hpkq6PxgPfu5rqocPbPC
t0FgJ4NXNmKOAuJllSlrrHATcUOhF4g5pX7tvOc8X4y7bvfwOmtw5ez3INKMF0q6
/y0vVCi6N1Z7CTa9eY8feZ1PImk/Fkq4NInSPyx7ZE3pLYmsvuJjliFrWo9TRVae
Dt5vvBKBOpAfhDiHkeXbX7Raj2B6c6adF4no/3SAVlAjIq1iBVjfQWyHAGUoEW1O
u3LdHTIb6gSTLJ4AfryEKrOE+1VMlYt92GwX692KKXMaJjytSQKCAQEA3pYbl8HD
Y++UyEN5VzWAQedT3//GDwpDfgdERe2E4smYrkVNJ2WAG2SqY1A35DIl8be3eHvl
soaL38j48ailfDYY9tI+IlapNh+VOLej+HiOytaPlLhcv2FpSC2qZT4EiU6IBXLo
+l6FrmD/VQXTjvoktzsDB/n1t4Dfa3Ogf+lLf1Jxr94YpEnDh18V5ofj78SplVLm
NrzsHxAafE4Ni2a7dyWjcDYIuL7FTShT+0K4W45tRr+CGxThxu7LEe7zw4Z1IagU
jJNtXjvDD/Zw4UTqI6RwWGZsu6UjPS6LHhOqnWqflWmFRIfMbDkuWvnGZTM9DkVg
kk1+BNi1PECZXwKCAQEAyMOjbVo6XV3lFN0X8TpHyg/z9ar00/SE7WEJHqPSuzYT
rSfU4vDDlaPAwkYvGi9ZKi9VM+R3CyBNxnK9Yq6NurHhhrYcAwdS/hGLT1K2o0Y8
Pgv7gZCFb+SIwLBhlUG9otGULcBzLneqgVUqyMG6IoCjuC2LRyB71Xc2UMyg6n/f
XpV2RTMb8f+26cgm6nj0SDAfgpr8HV6uNV80c6l1A8gq86nUWwiVAEUdmExSDe7J
shsfWAj8RSErqDXf1BtEdPLJUSIPX5VXkzAXOXIkengwVno0vv0dBN8uraS8iQSG
0JsJLLcw9b5kvnh6FEbE7POsIqKyCZV9VADwO6YW9QKCAQBYQsdwNqoGv6KMgozj
8tgHyfWtVduwbQ50M+dznwpZbzz2pY5Bd/MDabhSpyVyfBwlrAa5ZM+hKc7fDu7/
zDLKfR0LCjUPIrP4PS/LjK4dQZjFf6zxeOV2EedQcqMlgCEGXTh8iKMvXDm/+sBk
c2n/QNs8OM8r44b2m8h78B6NefGw6/0ekn/M7V72F9M0VWAh3Cauim+09tbePmFy
NvUR+MuPJEKZpSNyNltADCS49izqSSC1tAygNniMjHXDh6/rMS7TCLYVRARTIHlp
o/wAp3X8aiEOPJcTFRlTElihtYSq5POgqHXqxbpek5H5CyALUvT76rCvcsDspQ3A
dZEbAoIBAQCoLEmP5o8Rev/UdEgECB/uwWJIngYsLp3TAv/SrMRvkiL1X3JTD/+m
L9/eXVBDjPoR/khPCcg2h77ex2qhaTrL8wnKAG6CkvYQYb3impTnPIRmLT9nDxrX
2gY78wQrNUCXTRvlH1rcx90KLb+DH9S95ig+tdf/otRYwl27XU5GYQtJfcXuvZth
IiWku8btjpiCh909WHpsV81yY+faI08j9d8U8WQzRYMbEMpzsyrhBO/rxBCDfDNl
7R1W8JooYRb9KAs/bVqXZNBROW2a72RjOp6zMfdRLVHLrPC7AE32MNaFk/khfesD
T5OwgdcxeP6oxo2hDcw5fwHXBlo2fTCpAoIBAQChgjv5AfQ50spqvHy6MNem4tV0
L0IsxmNLsi8X2a6s4kStwUzOxDA8c/e54XabxQNZ0ERU1q+bgbG7PWC4twDMPR8i
2DO6rgqSK4MjGOTgAoeDuy3mElFQmCLRs04Wf4jh8kPi217WFlYBynh2HmBKbh42
JmIrLetbKEK13FXRvMkgZcX4OIDrT5TOvev4VZArU8PTRlWv3sqsKAVXjX0clGHf
I0/2kSsr2qq1UY7JrYWZsZ9uqz2ZH0pF19a6O/Cq4uqTYoL+sYzFTSeFmChRjV1g
ancTvTn9lcBqECDMgq5DE/p96Oxg/t8elalR6WDUlysafphVz3nTuyMTh7ka
-----END RSA PRIVATE KEY-----

View File

@@ -1,35 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIGETCCA/mgAwIBAgIUGl+MIvpEi0+TWzj0ieh6WtjXmP8wDQYJKoZIhvcNAQEL
BQAwgZYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH
DA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQLDAdBbmRy
b2lkMRIwEAYDVQQDDAlhcHBzZWFyY2gxIjAgBgkqhkiG9w0BCQEWE2FuZHJvaWRA
YW5kcm9pZC5jb20wIBcNMTkxMTIwMjMxNTM1WhgPNDc1NzEwMTYyMzE1MzVaMIGW
MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91
bnRhaW4gVmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDES
MBAGA1UEAwwJYXBwc2VhcmNoMSIwIAYJKoZIhvcNAQkBFhNhbmRyb2lkQGFuZHJv
aWQuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsyPlp3q3P9Xg
W1WhIwQiF9em9oqaGQ/3dbIxickAy591qcRbpHb4lDTZusRECfqlV215mV+lv5x4
EhOnId3uPKBAJ/YDtL7zUW6TWL7to7zEnUqSIKTcoQzNF2EiCeGuRhrtgYvAD3HQ
dwr4xrbSADbDArF04A49voLpsmq1fyNgl86VISiMRqoSLJnA6eghlduuOt+nf252
6WgxDs/JrO/eK70q0+RwmWzVJ/tVr+36a65N4EHhfL4t2hdV0k0XFob7hBn7XWzC
QrSR3jCvE3yAfAr3tq5c19/WWBA7V45nEHzXyAvBUHWubYvDi+vm/yzqU2rQwScC
bzp4zK4CnhBHqb4gHoy0+kfFIwJ1A3GT2pl3ba/NsIYgliMtPQfkDV5PE5RTNcwH
21ewH7vm2+spQv5Z/2TEV2lEHlp2vuAliyn2AT4u1ginr6vtBRFLmpPeziFcfB0y
7h04GctZpX8odz+XI7aMDe47RNu9XyJX0vulntxmlDF76k8Z9DIXg02hY+yc/i7+
2ztnj1eXL51p+HyhK5VbvJWbKkVaMQijlbuIMYNzMA6L0WHWRc2Cux9UDODMGoiC
w09JpqudCS/95I/F1xaWJ/Kh3vKeQshHAz0hrL7v7wpjmfeXf6NGsWJGy+giCwZj
ABtn9nFQoesgi7M1LeazD5Q/4v4AMaUCAwEAAaNTMFEwHQYDVR0OBBYEFJpHCy2Y
3qaL6cLpE9fe53L61KEEMB8GA1UdIwQYMBaAFJpHCy2Y3qaL6cLpE9fe53L61KEE
MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBAGDYAcOhbOYcDB2K
WDZka+FCORFFvz4nLQGE7Z9TAn1g7XusM2KbXlb2drIN6CWOFlnKQrUsNsAHrc+s
tl+A1vC3/NfYKKBVuizPx/kHUgz3k/UIJzbzEu/uCJd86idcJoUTqC/qEJAeeQqM
XpsNP1Yg7oyzZT8sFlUAKeDeXJ7fIDXR6nduUQ6uJXkee/5JF3VedHdgHAUsC19/
KHhyVU3MLDUNBdAmM79+DsdVYi2Pw31jojMu95Zz1MYTRBcgQAiEw5nncr38k6ac
Gy+JffgJR68FzI4QLBSxnDRFD2zXJ09lpP6Sjb1FVcDzk7Bi/EQDLBkrkbeLsk5F
a0xz9VoJ3kM7Cc4R9MXN4ZWuePjdJwgasnHmllsXn45R9odgJgmfzuUwtgNw/XKQ
QcQl7Q9QUrBCqIoHijxscUZCBSmIHVNBBDckRAmSXHeWMRlO3uBR4IA/Jfrt//4f
uc7CNUp+LQ6EzBXJOVFrXRtau6Oj+jM1+fzxKo1uV2+T+GdVEE5jeF/6nB3qna6h
2NmyLqbqeqp2QxgzBWSGy8Ugs6zg4wItJBqOoRLKKFxTJu5OAzJ4fUA+g7WFXNhR
kG56SJ863LZoORKHWE72oXYeIW98Tq0qKLH3NzH5L4tfX8DeBTq+APezHetH1ljA
D0avPy62g0i643bbpwZgezBgRIKL
-----END CERTIFICATE-----

View File

@@ -1,68 +0,0 @@
// Copyright (C) 2019 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package {
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_license"],
}
filegroup {
name: "framework-appsearch-sources",
srcs: [
":framework-appsearch-internal-sources",
":framework-appsearch-external-sources",
],
visibility: ["//frameworks/base"],
}
filegroup {
name: "framework-appsearch-internal-sources",
srcs: [
"java/**/*.java",
"java/**/*.aidl",
],
exclude_srcs: [":framework-appsearch-external-sources"],
path: "java",
}
filegroup {
name: "framework-appsearch-external-sources",
srcs: [
"java/external/**/*.java",
"java/external/**/*.aidl",
],
path: "java/external",
}
java_sdk_library {
name: "framework-appsearch",
srcs: [":framework-appsearch-sources"],
sdk_version: "module_current",
static_libs: [
// This list must be kept in sync with jarjar.txt
"modules-utils-preconditions",
],
defaults: ["framework-module-defaults"],
permitted_packages: ["android.app.appsearch"],
jarjar_rules: "jarjar-rules.txt",
apex_available: ["com.android.appsearch"],
impl_library_visibility: [
"//frameworks/base/apex/appsearch/service",
],
unsafe_ignore_missing_latest_api: true, // TODO(b/146218515) should be removed
}

View File

@@ -1,460 +0,0 @@
// Signature format: 2.0
package android.app.appsearch {
public final class AppSearchBatchResult<KeyType, ValueType> {
method @NonNull public java.util.Map<KeyType,android.app.appsearch.AppSearchResult<ValueType>> getAll();
method @NonNull public java.util.Map<KeyType,android.app.appsearch.AppSearchResult<ValueType>> getFailures();
method @NonNull public java.util.Map<KeyType,ValueType> getSuccesses();
method public boolean isSuccess();
}
public static final class AppSearchBatchResult.Builder<KeyType, ValueType> {
ctor public AppSearchBatchResult.Builder();
method @NonNull public android.app.appsearch.AppSearchBatchResult<KeyType,ValueType> build();
method @NonNull public android.app.appsearch.AppSearchBatchResult.Builder<KeyType,ValueType> setFailure(@NonNull KeyType, int, @Nullable String);
method @NonNull public android.app.appsearch.AppSearchBatchResult.Builder<KeyType,ValueType> setResult(@NonNull KeyType, @NonNull android.app.appsearch.AppSearchResult<ValueType>);
method @NonNull public android.app.appsearch.AppSearchBatchResult.Builder<KeyType,ValueType> setSuccess(@NonNull KeyType, @Nullable ValueType);
}
public class AppSearchManager {
method public void createGlobalSearchSession(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.GlobalSearchSession>>);
method public void createSearchSession(@NonNull android.app.appsearch.AppSearchManager.SearchContext, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.AppSearchSession>>);
}
public static final class AppSearchManager.SearchContext {
method @NonNull public String getDatabaseName();
}
public static final class AppSearchManager.SearchContext.Builder {
ctor public AppSearchManager.SearchContext.Builder(@NonNull String);
method @NonNull public android.app.appsearch.AppSearchManager.SearchContext build();
}
public final class AppSearchResult<ValueType> {
method @Nullable public String getErrorMessage();
method public int getResultCode();
method @Nullable public ValueType getResultValue();
method public boolean isSuccess();
method @NonNull public static <ValueType> android.app.appsearch.AppSearchResult<ValueType> newFailedResult(int, @Nullable String);
method @NonNull public static <ValueType> android.app.appsearch.AppSearchResult<ValueType> newSuccessfulResult(@Nullable ValueType);
field public static final int RESULT_INTERNAL_ERROR = 2; // 0x2
field public static final int RESULT_INVALID_ARGUMENT = 3; // 0x3
field public static final int RESULT_INVALID_SCHEMA = 7; // 0x7
field public static final int RESULT_IO_ERROR = 4; // 0x4
field public static final int RESULT_NOT_FOUND = 6; // 0x6
field public static final int RESULT_OK = 0; // 0x0
field public static final int RESULT_OUT_OF_SPACE = 5; // 0x5
field public static final int RESULT_SECURITY_ERROR = 8; // 0x8
field public static final int RESULT_UNKNOWN_ERROR = 1; // 0x1
}
public final class AppSearchSchema {
method @NonNull public java.util.List<android.app.appsearch.AppSearchSchema.PropertyConfig> getProperties();
method @NonNull public String getSchemaType();
}
public static final class AppSearchSchema.BooleanPropertyConfig extends android.app.appsearch.AppSearchSchema.PropertyConfig {
}
public static final class AppSearchSchema.BooleanPropertyConfig.Builder {
ctor public AppSearchSchema.BooleanPropertyConfig.Builder(@NonNull String);
method @NonNull public android.app.appsearch.AppSearchSchema.BooleanPropertyConfig build();
method @NonNull public android.app.appsearch.AppSearchSchema.BooleanPropertyConfig.Builder setCardinality(int);
}
public static final class AppSearchSchema.Builder {
ctor public AppSearchSchema.Builder(@NonNull String);
method @NonNull public android.app.appsearch.AppSearchSchema.Builder addProperty(@NonNull android.app.appsearch.AppSearchSchema.PropertyConfig);
method @NonNull public android.app.appsearch.AppSearchSchema build();
}
public static final class AppSearchSchema.BytesPropertyConfig extends android.app.appsearch.AppSearchSchema.PropertyConfig {
}
public static final class AppSearchSchema.BytesPropertyConfig.Builder {
ctor public AppSearchSchema.BytesPropertyConfig.Builder(@NonNull String);
method @NonNull public android.app.appsearch.AppSearchSchema.BytesPropertyConfig build();
method @NonNull public android.app.appsearch.AppSearchSchema.BytesPropertyConfig.Builder setCardinality(int);
}
public static final class AppSearchSchema.DocumentPropertyConfig extends android.app.appsearch.AppSearchSchema.PropertyConfig {
method @NonNull public String getSchemaType();
method public boolean shouldIndexNestedProperties();
}
public static final class AppSearchSchema.DocumentPropertyConfig.Builder {
ctor public AppSearchSchema.DocumentPropertyConfig.Builder(@NonNull String, @NonNull String);
method @NonNull public android.app.appsearch.AppSearchSchema.DocumentPropertyConfig build();
method @NonNull public android.app.appsearch.AppSearchSchema.DocumentPropertyConfig.Builder setCardinality(int);
method @NonNull public android.app.appsearch.AppSearchSchema.DocumentPropertyConfig.Builder setShouldIndexNestedProperties(boolean);
}
public static final class AppSearchSchema.DoublePropertyConfig extends android.app.appsearch.AppSearchSchema.PropertyConfig {
}
public static final class AppSearchSchema.DoublePropertyConfig.Builder {
ctor public AppSearchSchema.DoublePropertyConfig.Builder(@NonNull String);
method @NonNull public android.app.appsearch.AppSearchSchema.DoublePropertyConfig build();
method @NonNull public android.app.appsearch.AppSearchSchema.DoublePropertyConfig.Builder setCardinality(int);
}
public static final class AppSearchSchema.LongPropertyConfig extends android.app.appsearch.AppSearchSchema.PropertyConfig {
}
public static final class AppSearchSchema.LongPropertyConfig.Builder {
ctor public AppSearchSchema.LongPropertyConfig.Builder(@NonNull String);
method @NonNull public android.app.appsearch.AppSearchSchema.LongPropertyConfig build();
method @NonNull public android.app.appsearch.AppSearchSchema.LongPropertyConfig.Builder setCardinality(int);
}
public abstract static class AppSearchSchema.PropertyConfig {
method public int getCardinality();
method @NonNull public String getName();
field public static final int CARDINALITY_OPTIONAL = 2; // 0x2
field public static final int CARDINALITY_REPEATED = 1; // 0x1
field public static final int CARDINALITY_REQUIRED = 3; // 0x3
}
public static final class AppSearchSchema.StringPropertyConfig extends android.app.appsearch.AppSearchSchema.PropertyConfig {
method public int getIndexingType();
method public int getTokenizerType();
field public static final int INDEXING_TYPE_EXACT_TERMS = 1; // 0x1
field public static final int INDEXING_TYPE_NONE = 0; // 0x0
field public static final int INDEXING_TYPE_PREFIXES = 2; // 0x2
field public static final int TOKENIZER_TYPE_NONE = 0; // 0x0
field public static final int TOKENIZER_TYPE_PLAIN = 1; // 0x1
}
public static final class AppSearchSchema.StringPropertyConfig.Builder {
ctor public AppSearchSchema.StringPropertyConfig.Builder(@NonNull String);
method @NonNull public android.app.appsearch.AppSearchSchema.StringPropertyConfig build();
method @NonNull public android.app.appsearch.AppSearchSchema.StringPropertyConfig.Builder setCardinality(int);
method @NonNull public android.app.appsearch.AppSearchSchema.StringPropertyConfig.Builder setIndexingType(int);
method @NonNull public android.app.appsearch.AppSearchSchema.StringPropertyConfig.Builder setTokenizerType(int);
}
public final class AppSearchSession implements java.io.Closeable {
method public void close();
method public void getByDocumentId(@NonNull android.app.appsearch.GetByDocumentIdRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,android.app.appsearch.GenericDocument>);
method public void getNamespaces(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.util.Set<java.lang.String>>>);
method public void getSchema(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.GetSchemaResponse>>);
method public void getStorageInfo(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.StorageInfo>>);
method public void put(@NonNull android.app.appsearch.PutDocumentsRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,java.lang.Void>);
method public void remove(@NonNull android.app.appsearch.RemoveByDocumentIdRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,java.lang.Void>);
method public void remove(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.lang.Void>>);
method public void reportUsage(@NonNull android.app.appsearch.ReportUsageRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.lang.Void>>);
method @NonNull public android.app.appsearch.SearchResults search(@NonNull String, @NonNull android.app.appsearch.SearchSpec);
method public void setSchema(@NonNull android.app.appsearch.SetSchemaRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.SetSchemaResponse>>);
}
public interface BatchResultCallback<KeyType, ValueType> {
method public void onResult(@NonNull android.app.appsearch.AppSearchBatchResult<KeyType,ValueType>);
method public default void onSystemError(@Nullable Throwable);
}
public class GenericDocument {
ctor protected GenericDocument(@NonNull android.app.appsearch.GenericDocument);
method public long getCreationTimestampMillis();
method @NonNull public String getId();
method public static int getMaxIndexedProperties();
method @NonNull public String getNamespace();
method @Nullable public Object getProperty(@NonNull String);
method public boolean getPropertyBoolean(@NonNull String);
method @Nullable public boolean[] getPropertyBooleanArray(@NonNull String);
method @Nullable public byte[] getPropertyBytes(@NonNull String);
method @Nullable public byte[][] getPropertyBytesArray(@NonNull String);
method @Nullable public android.app.appsearch.GenericDocument getPropertyDocument(@NonNull String);
method @Nullable public android.app.appsearch.GenericDocument[] getPropertyDocumentArray(@NonNull String);
method public double getPropertyDouble(@NonNull String);
method @Nullable public double[] getPropertyDoubleArray(@NonNull String);
method public long getPropertyLong(@NonNull String);
method @Nullable public long[] getPropertyLongArray(@NonNull String);
method @NonNull public java.util.Set<java.lang.String> getPropertyNames();
method @Nullable public String getPropertyString(@NonNull String);
method @Nullable public String[] getPropertyStringArray(@NonNull String);
method @NonNull public String getSchemaType();
method public int getScore();
method public long getTtlMillis();
}
public static class GenericDocument.Builder<BuilderType extends android.app.appsearch.GenericDocument.Builder> {
ctor public GenericDocument.Builder(@NonNull String, @NonNull String, @NonNull String);
method @NonNull public android.app.appsearch.GenericDocument build();
method @NonNull public BuilderType setCreationTimestampMillis(long);
method @NonNull public BuilderType setPropertyBoolean(@NonNull String, @NonNull boolean...);
method @NonNull public BuilderType setPropertyBytes(@NonNull String, @NonNull byte[]...);
method @NonNull public BuilderType setPropertyDocument(@NonNull String, @NonNull android.app.appsearch.GenericDocument...);
method @NonNull public BuilderType setPropertyDouble(@NonNull String, @NonNull double...);
method @NonNull public BuilderType setPropertyLong(@NonNull String, @NonNull long...);
method @NonNull public BuilderType setPropertyString(@NonNull String, @NonNull java.lang.String...);
method @NonNull public BuilderType setScore(@IntRange(from=0, to=java.lang.Integer.MAX_VALUE) int);
method @NonNull public BuilderType setTtlMillis(long);
}
public final class GetByDocumentIdRequest {
method @NonNull public java.util.Set<java.lang.String> getIds();
method @NonNull public String getNamespace();
method @NonNull public java.util.Map<java.lang.String,java.util.List<java.lang.String>> getProjections();
field public static final String PROJECTION_SCHEMA_TYPE_WILDCARD = "*";
}
public static final class GetByDocumentIdRequest.Builder {
ctor public GetByDocumentIdRequest.Builder(@NonNull String);
method @NonNull public android.app.appsearch.GetByDocumentIdRequest.Builder addIds(@NonNull java.lang.String...);
method @NonNull public android.app.appsearch.GetByDocumentIdRequest.Builder addIds(@NonNull java.util.Collection<java.lang.String>);
method @NonNull public android.app.appsearch.GetByDocumentIdRequest.Builder addProjection(@NonNull String, @NonNull java.util.Collection<java.lang.String>);
method @NonNull public android.app.appsearch.GetByDocumentIdRequest build();
}
public final class GetSchemaResponse {
method @NonNull public java.util.Set<android.app.appsearch.AppSearchSchema> getSchemas();
method @IntRange(from=0) public int getVersion();
}
public static final class GetSchemaResponse.Builder {
ctor public GetSchemaResponse.Builder();
method @NonNull public android.app.appsearch.GetSchemaResponse.Builder addSchema(@NonNull android.app.appsearch.AppSearchSchema);
method @NonNull public android.app.appsearch.GetSchemaResponse build();
method @NonNull public android.app.appsearch.GetSchemaResponse.Builder setVersion(@IntRange(from=0) int);
}
public class GlobalSearchSession implements java.io.Closeable {
method public void close();
method public void reportSystemUsage(@NonNull android.app.appsearch.ReportSystemUsageRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.lang.Void>>);
method @NonNull public android.app.appsearch.SearchResults search(@NonNull String, @NonNull android.app.appsearch.SearchSpec);
}
public abstract class Migrator {
ctor public Migrator();
method @NonNull @WorkerThread public abstract android.app.appsearch.GenericDocument onDowngrade(int, int, @NonNull android.app.appsearch.GenericDocument);
method @NonNull @WorkerThread public abstract android.app.appsearch.GenericDocument onUpgrade(int, int, @NonNull android.app.appsearch.GenericDocument);
method public abstract boolean shouldMigrate(int, int);
}
public class PackageIdentifier {
ctor public PackageIdentifier(@NonNull String, @NonNull byte[]);
method @NonNull public String getPackageName();
method @NonNull public byte[] getSha256Certificate();
}
public final class PutDocumentsRequest {
method @NonNull public java.util.List<android.app.appsearch.GenericDocument> getGenericDocuments();
}
public static final class PutDocumentsRequest.Builder {
ctor public PutDocumentsRequest.Builder();
method @NonNull public android.app.appsearch.PutDocumentsRequest.Builder addGenericDocuments(@NonNull android.app.appsearch.GenericDocument...);
method @NonNull public android.app.appsearch.PutDocumentsRequest.Builder addGenericDocuments(@NonNull java.util.Collection<? extends android.app.appsearch.GenericDocument>);
method @NonNull public android.app.appsearch.PutDocumentsRequest build();
}
public final class RemoveByDocumentIdRequest {
method @NonNull public java.util.Set<java.lang.String> getIds();
method @NonNull public String getNamespace();
}
public static final class RemoveByDocumentIdRequest.Builder {
ctor public RemoveByDocumentIdRequest.Builder(@NonNull String);
method @NonNull public android.app.appsearch.RemoveByDocumentIdRequest.Builder addIds(@NonNull java.lang.String...);
method @NonNull public android.app.appsearch.RemoveByDocumentIdRequest.Builder addIds(@NonNull java.util.Collection<java.lang.String>);
method @NonNull public android.app.appsearch.RemoveByDocumentIdRequest build();
}
public final class ReportSystemUsageRequest {
method @NonNull public String getDatabaseName();
method @NonNull public String getDocumentId();
method @NonNull public String getNamespace();
method @NonNull public String getPackageName();
method public long getUsageTimestampMillis();
}
public static final class ReportSystemUsageRequest.Builder {
ctor public ReportSystemUsageRequest.Builder(@NonNull String, @NonNull String, @NonNull String, @NonNull String);
method @NonNull public android.app.appsearch.ReportSystemUsageRequest build();
method @NonNull public android.app.appsearch.ReportSystemUsageRequest.Builder setUsageTimestampMillis(long);
}
public final class ReportUsageRequest {
method @NonNull public String getDocumentId();
method @NonNull public String getNamespace();
method public long getUsageTimestampMillis();
}
public static final class ReportUsageRequest.Builder {
ctor public ReportUsageRequest.Builder(@NonNull String, @NonNull String);
method @NonNull public android.app.appsearch.ReportUsageRequest build();
method @NonNull public android.app.appsearch.ReportUsageRequest.Builder setUsageTimestampMillis(long);
}
public final class SearchResult {
method @NonNull public String getDatabaseName();
method @NonNull public android.app.appsearch.GenericDocument getGenericDocument();
method @NonNull public java.util.List<android.app.appsearch.SearchResult.MatchInfo> getMatchInfos();
method @NonNull public String getPackageName();
method public double getRankingSignal();
}
public static final class SearchResult.Builder {
ctor public SearchResult.Builder(@NonNull String, @NonNull String);
method @NonNull public android.app.appsearch.SearchResult.Builder addMatchInfo(@NonNull android.app.appsearch.SearchResult.MatchInfo);
method @NonNull public android.app.appsearch.SearchResult build();
method @NonNull public android.app.appsearch.SearchResult.Builder setGenericDocument(@NonNull android.app.appsearch.GenericDocument);
method @NonNull public android.app.appsearch.SearchResult.Builder setRankingSignal(double);
}
public static final class SearchResult.MatchInfo {
method @NonNull public CharSequence getExactMatch();
method @NonNull public android.app.appsearch.SearchResult.MatchRange getExactMatchRange();
method @NonNull public String getFullText();
method @NonNull public String getPropertyPath();
method @NonNull public CharSequence getSnippet();
method @NonNull public android.app.appsearch.SearchResult.MatchRange getSnippetRange();
}
public static final class SearchResult.MatchInfo.Builder {
ctor public SearchResult.MatchInfo.Builder(@NonNull String);
method @NonNull public android.app.appsearch.SearchResult.MatchInfo build();
method @NonNull public android.app.appsearch.SearchResult.MatchInfo.Builder setExactMatchRange(@NonNull android.app.appsearch.SearchResult.MatchRange);
method @NonNull public android.app.appsearch.SearchResult.MatchInfo.Builder setSnippetRange(@NonNull android.app.appsearch.SearchResult.MatchRange);
}
public static final class SearchResult.MatchRange {
ctor public SearchResult.MatchRange(int, int);
method public int getEnd();
method public int getStart();
}
public class SearchResults implements java.io.Closeable {
method public void close();
method public void getNextPage(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.util.List<android.app.appsearch.SearchResult>>>);
}
public final class SearchSpec {
method @NonNull public java.util.List<java.lang.String> getFilterNamespaces();
method @NonNull public java.util.List<java.lang.String> getFilterPackageNames();
method @NonNull public java.util.List<java.lang.String> getFilterSchemas();
method public int getMaxSnippetSize();
method public int getOrder();
method @NonNull public java.util.Map<java.lang.String,java.util.List<java.lang.String>> getProjections();
method public int getRankingStrategy();
method public int getResultCountPerPage();
method public int getResultGroupingLimit();
method public int getResultGroupingTypeFlags();
method public int getSnippetCount();
method public int getSnippetCountPerProperty();
method public int getTermMatch();
field public static final int GROUPING_TYPE_PER_NAMESPACE = 2; // 0x2
field public static final int GROUPING_TYPE_PER_PACKAGE = 1; // 0x1
field public static final int ORDER_ASCENDING = 1; // 0x1
field public static final int ORDER_DESCENDING = 0; // 0x0
field public static final String PROJECTION_SCHEMA_TYPE_WILDCARD = "*";
field public static final int RANKING_STRATEGY_CREATION_TIMESTAMP = 2; // 0x2
field public static final int RANKING_STRATEGY_DOCUMENT_SCORE = 1; // 0x1
field public static final int RANKING_STRATEGY_NONE = 0; // 0x0
field public static final int RANKING_STRATEGY_RELEVANCE_SCORE = 3; // 0x3
field public static final int RANKING_STRATEGY_SYSTEM_USAGE_COUNT = 6; // 0x6
field public static final int RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP = 7; // 0x7
field public static final int RANKING_STRATEGY_USAGE_COUNT = 4; // 0x4
field public static final int RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP = 5; // 0x5
field public static final int TERM_MATCH_EXACT_ONLY = 1; // 0x1
field public static final int TERM_MATCH_PREFIX = 2; // 0x2
}
public static final class SearchSpec.Builder {
ctor public SearchSpec.Builder();
method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterNamespaces(@NonNull java.lang.String...);
method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterNamespaces(@NonNull java.util.Collection<java.lang.String>);
method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterPackageNames(@NonNull java.lang.String...);
method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterPackageNames(@NonNull java.util.Collection<java.lang.String>);
method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterSchemas(@NonNull java.lang.String...);
method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterSchemas(@NonNull java.util.Collection<java.lang.String>);
method @NonNull public android.app.appsearch.SearchSpec.Builder addProjection(@NonNull String, @NonNull java.util.Collection<java.lang.String>);
method @NonNull public android.app.appsearch.SearchSpec build();
method @NonNull public android.app.appsearch.SearchSpec.Builder setMaxSnippetSize(@IntRange(from=0, to=0x2710) int);
method @NonNull public android.app.appsearch.SearchSpec.Builder setOrder(int);
method @NonNull public android.app.appsearch.SearchSpec.Builder setRankingStrategy(int);
method @NonNull public android.app.appsearch.SearchSpec.Builder setResultCountPerPage(@IntRange(from=0, to=0x2710) int);
method @NonNull public android.app.appsearch.SearchSpec.Builder setResultGrouping(int, int);
method @NonNull public android.app.appsearch.SearchSpec.Builder setSnippetCount(@IntRange(from=0, to=0x2710) int);
method @NonNull public android.app.appsearch.SearchSpec.Builder setSnippetCountPerProperty(@IntRange(from=0, to=0x2710) int);
method @NonNull public android.app.appsearch.SearchSpec.Builder setTermMatch(int);
}
public final class SetSchemaRequest {
method @NonNull public java.util.Map<java.lang.String,android.app.appsearch.Migrator> getMigrators();
method @NonNull public java.util.Set<android.app.appsearch.AppSearchSchema> getSchemas();
method @NonNull public java.util.Set<java.lang.String> getSchemasNotDisplayedBySystem();
method @NonNull public java.util.Map<java.lang.String,java.util.Set<android.app.appsearch.PackageIdentifier>> getSchemasVisibleToPackages();
method @IntRange(from=1) public int getVersion();
method public boolean isForceOverride();
}
public static final class SetSchemaRequest.Builder {
ctor public SetSchemaRequest.Builder();
method @NonNull public android.app.appsearch.SetSchemaRequest.Builder addSchemas(@NonNull android.app.appsearch.AppSearchSchema...);
method @NonNull public android.app.appsearch.SetSchemaRequest.Builder addSchemas(@NonNull java.util.Collection<android.app.appsearch.AppSearchSchema>);
method @NonNull public android.app.appsearch.SetSchemaRequest build();
method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setForceOverride(boolean);
method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setMigrator(@NonNull String, @NonNull android.app.appsearch.Migrator);
method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setMigrators(@NonNull java.util.Map<java.lang.String,android.app.appsearch.Migrator>);
method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setSchemaTypeDisplayedBySystem(@NonNull String, boolean);
method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setSchemaTypeVisibilityForPackage(@NonNull String, boolean, @NonNull android.app.appsearch.PackageIdentifier);
method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setVersion(@IntRange(from=1) int);
}
public class SetSchemaResponse {
method @NonNull public java.util.Set<java.lang.String> getDeletedTypes();
method @NonNull public java.util.Set<java.lang.String> getIncompatibleTypes();
method @NonNull public java.util.Set<java.lang.String> getMigratedTypes();
method @NonNull public java.util.List<android.app.appsearch.SetSchemaResponse.MigrationFailure> getMigrationFailures();
}
public static final class SetSchemaResponse.Builder {
ctor public SetSchemaResponse.Builder();
method @NonNull public android.app.appsearch.SetSchemaResponse.Builder addDeletedType(@NonNull String);
method @NonNull public android.app.appsearch.SetSchemaResponse.Builder addDeletedTypes(@NonNull java.util.Collection<java.lang.String>);
method @NonNull public android.app.appsearch.SetSchemaResponse.Builder addIncompatibleType(@NonNull String);
method @NonNull public android.app.appsearch.SetSchemaResponse.Builder addIncompatibleTypes(@NonNull java.util.Collection<java.lang.String>);
method @NonNull public android.app.appsearch.SetSchemaResponse.Builder addMigratedType(@NonNull String);
method @NonNull public android.app.appsearch.SetSchemaResponse.Builder addMigratedTypes(@NonNull java.util.Collection<java.lang.String>);
method @NonNull public android.app.appsearch.SetSchemaResponse.Builder addMigrationFailure(@NonNull android.app.appsearch.SetSchemaResponse.MigrationFailure);
method @NonNull public android.app.appsearch.SetSchemaResponse.Builder addMigrationFailures(@NonNull java.util.Collection<android.app.appsearch.SetSchemaResponse.MigrationFailure>);
method @NonNull public android.app.appsearch.SetSchemaResponse build();
}
public static class SetSchemaResponse.MigrationFailure {
ctor public SetSchemaResponse.MigrationFailure(@NonNull String, @NonNull String, @NonNull String, @NonNull android.app.appsearch.AppSearchResult<?>);
method @NonNull public android.app.appsearch.AppSearchResult<java.lang.Void> getAppSearchResult();
method @NonNull public String getDocumentId();
method @NonNull public String getNamespace();
method @NonNull public String getSchemaType();
}
public class StorageInfo {
method public int getAliveDocumentsCount();
method public int getAliveNamespacesCount();
method public long getSizeBytes();
}
public static final class StorageInfo.Builder {
ctor public StorageInfo.Builder();
method @NonNull public android.app.appsearch.StorageInfo build();
method @NonNull public android.app.appsearch.StorageInfo.Builder setAliveDocumentsCount(int);
method @NonNull public android.app.appsearch.StorageInfo.Builder setAliveNamespacesCount(int);
method @NonNull public android.app.appsearch.StorageInfo.Builder setSizeBytes(long);
}
}
package android.app.appsearch.exceptions {
public class AppSearchException extends java.lang.Exception {
ctor public AppSearchException(int);
ctor public AppSearchException(int, @Nullable String);
ctor public AppSearchException(int, @Nullable String, @Nullable Throwable);
method public int getResultCode();
method @NonNull public <T> android.app.appsearch.AppSearchResult<T> toAppSearchResult();
}
}

View File

@@ -1 +0,0 @@
// Signature format: 2.0

View File

@@ -1 +0,0 @@
// Signature format: 2.0

View File

@@ -1 +0,0 @@
// Signature format: 2.0

View File

@@ -1,9 +0,0 @@
// Signature format: 2.0
package android.app.appsearch {
public class AppSearchManagerFrameworkInitializer {
method public static void initialize();
}
}

View File

@@ -1 +0,0 @@
// Signature format: 2.0

View File

@@ -1,6 +0,0 @@
# Rename all com.android.internal.util classes to prevent class name collisions
# between this module and the other versions of the utility classes linked into
# the framework.
# These must be kept in sync with the sources of framework-utils-appsearch
rule com.android.internal.util.Preconditions* android.app.appsearch.internal.util.Preconditions@1

View File

@@ -1,7 +0,0 @@
{
"imports": [
{
"path": "frameworks/base/apex/appsearch/service/java/com/android/server/appsearch"
}
]
}

View File

@@ -1,242 +0,0 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.SystemService;
import android.annotation.UserHandleAware;
import android.app.appsearch.aidl.IAppSearchManager;
import android.content.Context;
import com.android.internal.util.Preconditions;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
* Provides access to the centralized AppSearch index maintained by the system.
*
* <p>AppSearch is an offline, on-device search library for managing structured data featuring:
*
* <ul>
* <li>APIs to index and retrieve data via full-text search.
* <li>An API for applications to explicitly grant read-access permission of their data to other
* applications.
* <b>See: {@link SetSchemaRequest.Builder#setSchemaTypeVisibilityForPackage}</b>
* <li>An API for applications to opt into or out of having their data displayed on System UI
* surfaces by the System-designated global querier.
* <b>See: {@link SetSchemaRequest.Builder#setSchemaTypeDisplayedBySystem}</b>
* </ul>
*
* <p>Applications create a database by opening an {@link AppSearchSession}.
*
* <p>Example:
*
* <pre>
* AppSearchManager appSearchManager = context.getSystemService(AppSearchManager.class);
*
* AppSearchManager.SearchContext searchContext = new AppSearchManager.SearchContext.Builder().
* setDatabaseName(dbName).build());
* appSearchManager.createSearchSession(searchContext, mExecutor, appSearchSessionResult -&gt; {
* mAppSearchSession = appSearchSessionResult.getResultValue();
* });</pre>
*
* <p>After opening the session, a schema must be set in order to define the organizational
* structure of data. The schema is set by calling {@link AppSearchSession#setSchema}. The schema is
* composed of a collection of {@link AppSearchSchema} objects, each of which defines a unique type
* of data.
*
* <p>Example:
*
* <pre>
* AppSearchSchema emailSchemaType = new AppSearchSchema.Builder("Email")
* .addProperty(new StringPropertyConfig.Builder("subject")
* .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
* .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
* .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
* .build()
* ).build();
*
* SetSchemaRequest request = new SetSchemaRequest.Builder().addSchema(emailSchemaType).build();
* mAppSearchSession.set(request, mExecutor, appSearchResult -&gt; {
* if (appSearchResult.isSuccess()) {
* //Schema has been successfully set.
* }
* });</pre>
*
* <p>The basic unit of data in AppSearch is represented as a {@link GenericDocument} object,
* containing an ID, namespace, time-to-live, score, and properties. A namespace organizes a logical
* group of documents. For example, a namespace can be created to group documents on a per-account
* basis. An ID identifies a single document within a namespace. The combination of namespace and ID
* uniquely identifies a {@link GenericDocument} in the database.
*
* <p>Once the schema has been set, {@link GenericDocument} objects can be put into the database and
* indexed by calling {@link AppSearchSession#put}.
*
* <p>Example:
*
* <pre>
* // Although for this example we use GenericDocument directly, we recommend extending
* // GenericDocument to create specific types (i.e. Email) with specific setters/getters.
* GenericDocument email = new GenericDocument.Builder<>(NAMESPACE, ID, EMAIL_SCHEMA_TYPE)
* .setPropertyString(“subject”, EMAIL_SUBJECT)
* .setScore(EMAIL_SCORE)
* .build();
*
* PutDocumentsRequest request = new PutDocumentsRequest.Builder().addGenericDocuments(email)
* .build();
* mAppSearchSession.put(request, mExecutor, appSearchBatchResult -&gt; {
* if (appSearchBatchResult.isSuccess()) {
* //All documents have been successfully indexed.
* }
* });</pre>
*
* <p>Searching within the database is done by calling {@link AppSearchSession#search} and providing
* the query string to search for, as well as a {@link SearchSpec}.
*
* <p>Alternatively, {@link AppSearchSession#getByDocumentId} can be called to retrieve documents by
* namespace and ID.
*
* <p>Document removal is done either by time-to-live expiration, or explicitly calling a remove
* operation. Remove operations can be done by namespace and ID via {@link
* AppSearchSession#remove(RemoveByDocumentIdRequest, Executor, BatchResultCallback)}, or by query
* via {@link AppSearchSession#remove(String, SearchSpec, Executor, Consumer)}.
*/
@SystemService(Context.APP_SEARCH_SERVICE)
public class AppSearchManager {
private final IAppSearchManager mService;
private final Context mContext;
/** @hide */
public AppSearchManager(@NonNull Context context, @NonNull IAppSearchManager service) {
mContext = Objects.requireNonNull(context);
mService = Objects.requireNonNull(service);
}
/** Contains information about how to create the search session. */
public static final class SearchContext {
final String mDatabaseName;
SearchContext(@NonNull String databaseName) {
mDatabaseName = Objects.requireNonNull(databaseName);
}
/**
* Returns the name of the database to create or open.
*
* <p>Databases with different names are fully separate with distinct types, namespaces, and
* data.
*/
@NonNull
public String getDatabaseName() {
return mDatabaseName;
}
/** Builder for {@link SearchContext} objects. */
public static final class Builder {
private final String mDatabaseName;
private boolean mBuilt = false;
/**
* Creates a new {@link SearchContext.Builder}.
*
* <p>{@link AppSearchSession} will create or open a database under the given name.
*
* <p>Databases with different names are fully separate with distinct types, namespaces,
* and data.
*
* <p>Database name cannot contain {@code '/'}.
*
* @param databaseName The name of the database.
* @throws IllegalArgumentException if the databaseName contains {@code '/'}.
*/
public Builder(@NonNull String databaseName) {
Objects.requireNonNull(databaseName);
Preconditions.checkArgument(
!databaseName.contains("/"), "Database name cannot contain '/'");
mDatabaseName = databaseName;
}
/** Builds a {@link SearchContext} instance. */
@NonNull
public SearchContext build() {
Preconditions.checkState(!mBuilt, "Builder has already been used");
mBuilt = true;
return new SearchContext(mDatabaseName);
}
}
}
/**
* Creates a new {@link AppSearchSession}.
*
* <p>This process requires an AppSearch native indexing file system. If it's not created, the
* initialization process will create one under the user's credential encrypted directory.
*
* @param searchContext The {@link SearchContext} contains all information to create a new
* {@link AppSearchSession}
* @param executor Executor on which to invoke the callback.
* @param callback The {@link AppSearchResult}&lt;{@link AppSearchSession}&gt; of performing
* this operation. Or a {@link AppSearchResult} with failure reason code and error
* information.
*/
@UserHandleAware
public void createSearchSession(
@NonNull SearchContext searchContext,
@NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<AppSearchResult<AppSearchSession>> callback) {
Objects.requireNonNull(searchContext);
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
AppSearchSession.createSearchSession(
searchContext,
mService,
mContext.getUser(),
getPackageName(),
executor,
callback);
}
/**
* Creates a new {@link GlobalSearchSession}.
*
* <p>This process requires an AppSearch native indexing file system. If it's not created, the
* initialization process will create one under the user's credential encrypted directory.
*
* @param executor Executor on which to invoke the callback.
* @param callback The {@link AppSearchResult}&lt;{@link GlobalSearchSession}&gt; of performing
* this operation. Or a {@link AppSearchResult} with failure reason code and error
* information.
*/
@UserHandleAware
public void createGlobalSearchSession(
@NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<AppSearchResult<GlobalSearchSession>> callback) {
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
GlobalSearchSession.createGlobalSearchSession(
mService, mContext.getUser(), getPackageName(), executor, callback);
}
/** Returns the package name that should be used for uid verification. */
@NonNull
private String getPackageName() {
return mContext.getOpPackageName();
}
}

View File

@@ -1,45 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch;
import android.annotation.SystemApi;
import android.app.SystemServiceRegistry;
import android.app.appsearch.aidl.IAppSearchManager;
import android.content.Context;
/**
* Class holding initialization code for the AppSearch module.
*
* @hide
*/
@SystemApi
public class AppSearchManagerFrameworkInitializer {
private AppSearchManagerFrameworkInitializer() {}
/**
* Called by {@link SystemServiceRegistry}'s static initializer and registers all AppSearch
* services to {@link Context}, so that {@link Context#getSystemService} can return them.
*
* @throws IllegalStateException if this is called from anywhere besides
* {@link SystemServiceRegistry}
*/
public static void initialize() {
SystemServiceRegistry.registerContextAwareService(
Context.APP_SEARCH_SERVICE, AppSearchManager.class,
(context, service) ->
new AppSearchManager(context, IAppSearchManager.Stub.asInterface(service)));
}
}

View File

@@ -1,271 +0,0 @@
/*
* 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 android.app.appsearch;
import static android.app.appsearch.AppSearchResult.RESULT_INVALID_SCHEMA;
import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
import static android.os.ParcelFileDescriptor.MODE_WRITE_ONLY;
import android.annotation.NonNull;
import android.annotation.WorkerThread;
import android.app.appsearch.aidl.AppSearchResultParcel;
import android.app.appsearch.aidl.IAppSearchManager;
import android.app.appsearch.aidl.IAppSearchResultCallback;
import android.app.appsearch.exceptions.AppSearchException;
import android.os.Bundle;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArraySet;
import java.io.Closeable;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
/**
* The helper class for {@link AppSearchSchema} migration.
*
* <p>It will query and migrate {@link GenericDocument} in given type to a new version.
* @hide
*/
public class AppSearchMigrationHelper implements Closeable {
private final IAppSearchManager mService;
private final String mPackageName;
private final String mDatabaseName;
private final UserHandle mUserHandle;
private final File mMigratedFile;
private final Set<String> mDestinationTypes;
private boolean mAreDocumentsMigrated = false;
AppSearchMigrationHelper(@NonNull IAppSearchManager service,
@NonNull UserHandle userHandle,
@NonNull String packageName,
@NonNull String databaseName,
@NonNull Set<AppSearchSchema> newSchemas) throws IOException {
mService = Objects.requireNonNull(service);
mUserHandle = Objects.requireNonNull(userHandle);
mPackageName = Objects.requireNonNull(packageName);
mDatabaseName = Objects.requireNonNull(databaseName);
mMigratedFile = File.createTempFile(/*prefix=*/"appsearch", /*suffix=*/null);
mDestinationTypes = new ArraySet<>(newSchemas.size());
for (AppSearchSchema newSchema : newSchemas) {
mDestinationTypes.add(newSchema.getSchemaType());
}
}
/**
* Queries all documents that need to be migrated to a different version and transform
* documents to that version by passing them to the provided {@link Migrator}.
*
* <p>The method will be executed on the executor provided to
* {@link AppSearchSession#setSchema}.
*
* @param schemaType The schema type that needs to be updated and whose {@link GenericDocument}
* need to be migrated.
* @param migrator The {@link Migrator} that will upgrade or downgrade a {@link
* GenericDocument} to new version.
*/
@WorkerThread
public void queryAndTransform(@NonNull String schemaType, @NonNull Migrator migrator,
int currentVersion, int finalVersion)
throws IOException, AppSearchException, InterruptedException, ExecutionException {
File queryFile = File.createTempFile(/*prefix=*/"appsearch", /*suffix=*/null);
try (ParcelFileDescriptor fileDescriptor =
ParcelFileDescriptor.open(queryFile, MODE_WRITE_ONLY)) {
CompletableFuture<AppSearchResult<Void>> future = new CompletableFuture<>();
mService.writeQueryResultsToFile(mPackageName, mDatabaseName,
fileDescriptor,
/*queryExpression=*/ "",
new SearchSpec.Builder()
.addFilterSchemas(schemaType)
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.build().getBundle(),
mUserHandle,
new IAppSearchResultCallback.Stub() {
@Override
public void onResult(AppSearchResultParcel resultParcel) {
future.complete(resultParcel.getResult());
}
});
AppSearchResult<Void> result = future.get();
if (!result.isSuccess()) {
throw new AppSearchException(result.getResultCode(), result.getErrorMessage());
}
readAndTransform(queryFile, migrator, currentVersion, finalVersion);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} finally {
queryFile.delete();
}
}
/**
* Puts all {@link GenericDocument} migrated from the previous call to
* {@link #queryAndTransform} into AppSearch.
*
* <p> This method should be only called once.
*
* @param responseBuilder a SetSchemaResponse builder whose result will be returned by this
* function with any
* {@link android.app.appsearch.SetSchemaResponse.MigrationFailure}
* added in.
* @return the {@link SetSchemaResponse} for {@link AppSearchSession#setSchema} call.
*/
@NonNull
AppSearchResult<SetSchemaResponse> putMigratedDocuments(
@NonNull SetSchemaResponse.Builder responseBuilder) {
if (!mAreDocumentsMigrated) {
return AppSearchResult.newSuccessfulResult(responseBuilder.build());
}
try (ParcelFileDescriptor fileDescriptor =
ParcelFileDescriptor.open(mMigratedFile, MODE_READ_ONLY)) {
CompletableFuture<AppSearchResult<List<Bundle>>> future = new CompletableFuture<>();
mService.putDocumentsFromFile(mPackageName, mDatabaseName, fileDescriptor, mUserHandle,
new IAppSearchResultCallback.Stub() {
@Override
public void onResult(AppSearchResultParcel resultParcel) {
future.complete(resultParcel.getResult());
}
});
AppSearchResult<List<Bundle>> result = future.get();
if (!result.isSuccess()) {
return AppSearchResult.newFailedResult(result);
}
List<Bundle> migratedFailureBundles = result.getResultValue();
for (int i = 0; i < migratedFailureBundles.size(); i++) {
responseBuilder.addMigrationFailure(
new SetSchemaResponse.MigrationFailure(migratedFailureBundles.get(i)));
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (Throwable t) {
return AppSearchResult.throwableToFailedResult(t);
} finally {
mMigratedFile.delete();
}
return AppSearchResult.newSuccessfulResult(responseBuilder.build());
}
/**
* Reads all saved {@link GenericDocument}s from the given {@link File}.
*
* <p>Transforms those {@link GenericDocument}s to the final version.
*
* <p>Save migrated {@link GenericDocument}s to the {@link #mMigratedFile}.
*/
private void readAndTransform(@NonNull File file, @NonNull Migrator migrator,
int currentVersion, int finalVersion)
throws IOException, AppSearchException {
try (DataInputStream inputStream = new DataInputStream(new FileInputStream(file));
DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(
mMigratedFile, /*append=*/ true))) {
GenericDocument document;
while (true) {
try {
document = readDocumentFromInputStream(inputStream);
} catch (EOFException e) {
break;
// Nothing wrong. We just finished reading.
}
GenericDocument newDocument;
if (currentVersion < finalVersion) {
newDocument = migrator.onUpgrade(currentVersion, finalVersion, document);
} else {
// currentVersion == finalVersion case won't trigger migration and get here.
newDocument = migrator.onDowngrade(currentVersion, finalVersion, document);
}
if (!mDestinationTypes.contains(newDocument.getSchemaType())) {
// we exit before the new schema has been set to AppSearch. So no
// observable changes will be applied to stored schemas and documents.
// And the temp file will be deleted at close(), which will be triggered at
// the end of try-with-resources block of SearchSessionImpl.
throw new AppSearchException(
RESULT_INVALID_SCHEMA,
"Receive a migrated document with schema type: "
+ newDocument.getSchemaType()
+ ". But the schema types doesn't exist in the request");
}
writeBundleToOutputStream(outputStream, newDocument.getBundle());
}
mAreDocumentsMigrated = true;
}
}
/**
* Reads the {@link Bundle} of a {@link GenericDocument} from given {@link DataInputStream}.
*
* @param inputStream The inputStream to read from
*
* @throws IOException on read failure.
* @throws EOFException if {@link java.io.InputStream} reaches the end.
*/
@NonNull
public static GenericDocument readDocumentFromInputStream(
@NonNull DataInputStream inputStream) throws IOException {
int length = inputStream.readInt();
if (length == 0) {
throw new EOFException();
}
byte[] serializedMessage = new byte[length];
inputStream.read(serializedMessage);
Parcel parcel = Parcel.obtain();
try {
parcel.unmarshall(serializedMessage, 0, serializedMessage.length);
parcel.setDataPosition(0);
Bundle bundle = parcel.readBundle();
return new GenericDocument(bundle);
} finally {
parcel.recycle();
}
}
/**
* Serializes a {@link Bundle} and writes into the given {@link DataOutputStream}.
*/
public static void writeBundleToOutputStream(
@NonNull DataOutputStream outputStream, @NonNull Bundle bundle)
throws IOException {
Parcel parcel = Parcel.obtain();
try {
parcel.writeBundle(bundle);
byte[] serializedMessage = parcel.marshall();
outputStream.writeInt(serializedMessage.length);
outputStream.write(serializedMessage);
} finally {
parcel.recycle();
}
}
@Override
public void close() throws IOException {
mMigratedFile.delete();
}
}

View File

@@ -1,885 +0,0 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.app.appsearch.aidl.AppSearchBatchResultParcel;
import android.app.appsearch.aidl.AppSearchResultParcel;
import android.app.appsearch.aidl.IAppSearchBatchResultCallback;
import android.app.appsearch.aidl.IAppSearchManager;
import android.app.appsearch.aidl.IAppSearchResultCallback;
import android.app.appsearch.exceptions.AppSearchException;
import android.app.appsearch.util.SchemaMigrationUtil;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import com.android.internal.util.Preconditions;
import java.io.Closeable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
* Provides a connection to a single AppSearch database.
*
* <p>An {@link AppSearchSession} instance provides access to database operations such as
* setting a schema, adding documents, and searching.
*
* <p>This class is thread safe.
*
* @see GlobalSearchSession
*/
public final class AppSearchSession implements Closeable {
private static final String TAG = "AppSearchSession";
private final String mPackageName;
private final String mDatabaseName;
private final UserHandle mUserHandle;
private final IAppSearchManager mService;
private boolean mIsMutated = false;
private boolean mIsClosed = false;
/**
* Creates a search session for the client, defined by the {@code userHandle} and
* {@code packageName}.
*/
static void createSearchSession(
@NonNull AppSearchManager.SearchContext searchContext,
@NonNull IAppSearchManager service,
@NonNull UserHandle userHandle,
@NonNull String packageName,
@NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<AppSearchResult<AppSearchSession>> callback) {
AppSearchSession searchSession =
new AppSearchSession(service, userHandle, packageName, searchContext.mDatabaseName);
searchSession.initialize(executor, callback);
}
// NOTE: No instance of this class should be created or returned except via initialize().
// Once the callback.accept has been called here, the class is ready to use.
private void initialize(
@NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<AppSearchResult<AppSearchSession>> callback) {
try {
mService.initialize(
mPackageName,
mUserHandle,
/*binderCallStartTimeMillis=*/ SystemClock.elapsedRealtime(),
new IAppSearchResultCallback.Stub() {
@Override
public void onResult(AppSearchResultParcel resultParcel) {
executor.execute(() -> {
AppSearchResult<Void> result = resultParcel.getResult();
if (result.isSuccess()) {
callback.accept(
AppSearchResult.newSuccessfulResult(
AppSearchSession.this));
} else {
callback.accept(AppSearchResult.newFailedResult(result));
}
});
}
});
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
private AppSearchSession(@NonNull IAppSearchManager service, @NonNull UserHandle userHandle,
@NonNull String packageName, @NonNull String databaseName) {
mService = service;
mUserHandle = userHandle;
mPackageName = packageName;
mDatabaseName = databaseName;
}
/**
* Sets the schema that represents the organizational structure of data within the AppSearch
* database.
*
* <p>Upon creating an {@link AppSearchSession}, {@link #setSchema} should be called. If the
* schema needs to be updated, or it has not been previously set, then the provided schema will
* be saved and persisted to disk. Otherwise, {@link #setSchema} is handled efficiently as a
* no-op call.
*
* @param request the schema to set or update the AppSearch database to.
* @param workExecutor Executor on which to schedule heavy client-side background work such as
* transforming documents.
* @param callbackExecutor Executor on which to invoke the callback.
* @param callback Callback to receive errors resulting from setting the schema. If the
* operation succeeds, the callback will be invoked with {@code null}.
*/
public void setSchema(
@NonNull SetSchemaRequest request,
@NonNull Executor workExecutor,
@NonNull @CallbackExecutor Executor callbackExecutor,
@NonNull Consumer<AppSearchResult<SetSchemaResponse>> callback) {
Objects.requireNonNull(request);
Objects.requireNonNull(workExecutor);
Objects.requireNonNull(callbackExecutor);
Objects.requireNonNull(callback);
Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed");
List<Bundle> schemaBundles = new ArrayList<>(request.getSchemas().size());
for (AppSearchSchema schema : request.getSchemas()) {
schemaBundles.add(schema.getBundle());
}
Map<String, List<Bundle>> schemasVisibleToPackagesBundles =
new ArrayMap<>(request.getSchemasVisibleToPackagesInternal().size());
for (Map.Entry<String, Set<PackageIdentifier>> entry :
request.getSchemasVisibleToPackagesInternal().entrySet()) {
List<Bundle> packageIdentifierBundles = new ArrayList<>(entry.getValue().size());
for (PackageIdentifier packageIdentifier : entry.getValue()) {
packageIdentifierBundles.add(packageIdentifier.getBundle());
}
schemasVisibleToPackagesBundles.put(entry.getKey(), packageIdentifierBundles);
}
// No need to trigger migration if user never set migrator
if (request.getMigrators().isEmpty()) {
setSchemaNoMigrations(
request,
schemaBundles,
schemasVisibleToPackagesBundles,
callbackExecutor,
callback);
} else {
setSchemaWithMigrations(
request,
schemaBundles,
schemasVisibleToPackagesBundles,
workExecutor,
callbackExecutor,
callback);
}
mIsMutated = true;
}
/**
* Retrieves the schema most recently successfully provided to {@link #setSchema}.
*
* @param executor Executor on which to invoke the callback.
* @param callback Callback to receive the pending results of schema.
*/
public void getSchema(
@NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<AppSearchResult<GetSchemaResponse>> callback) {
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed");
try {
mService.getSchema(
mPackageName,
mDatabaseName,
mUserHandle,
new IAppSearchResultCallback.Stub() {
@Override
public void onResult(AppSearchResultParcel resultParcel) {
executor.execute(() -> {
AppSearchResult<Bundle> result = resultParcel.getResult();
if (result.isSuccess()) {
GetSchemaResponse response =
new GetSchemaResponse(result.getResultValue());
callback.accept(AppSearchResult.newSuccessfulResult(response));
} else {
callback.accept(AppSearchResult.newFailedResult(result));
}
});
}
});
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Retrieves the set of all namespaces in the current database with at least one document.
*
* @param executor Executor on which to invoke the callback.
* @param callback Callback to receive the namespaces.
*/
public void getNamespaces(
@NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<AppSearchResult<Set<String>>> callback) {
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed");
try {
mService.getNamespaces(
mPackageName,
mDatabaseName,
mUserHandle,
new IAppSearchResultCallback.Stub() {
@Override
public void onResult(AppSearchResultParcel resultParcel) {
executor.execute(() -> {
AppSearchResult<List<String>> result = resultParcel.getResult();
if (result.isSuccess()) {
Set<String> namespaces =
new ArraySet<>(result.getResultValue());
callback.accept(
AppSearchResult.newSuccessfulResult(namespaces));
} else {
callback.accept(AppSearchResult.newFailedResult(result));
}
});
}
});
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Indexes documents into the {@link AppSearchSession} database.
*
* <p>Each {@link GenericDocument} object must have a {@code schemaType} field set to an {@link
* AppSearchSchema} type that has been previously registered by calling the {@link #setSchema}
* method.
*
* @param request containing documents to be indexed.
* @param executor Executor on which to invoke the callback.
* @param callback Callback to receive pending result of performing this operation. The keys
* of the returned {@link AppSearchBatchResult} are the IDs of the input
* documents. The values are {@code null} if they were successfully indexed,
* or a failed {@link AppSearchResult} otherwise. If an unexpected internal
* error occurs in the AppSearch service,
* {@link BatchResultCallback#onSystemError} will be invoked with a
* {@link Throwable}.
*/
public void put(
@NonNull PutDocumentsRequest request,
@NonNull @CallbackExecutor Executor executor,
@NonNull BatchResultCallback<String, Void> callback) {
Objects.requireNonNull(request);
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed");
List<GenericDocument> documents = request.getGenericDocuments();
List<Bundle> documentBundles = new ArrayList<>(documents.size());
for (int i = 0; i < documents.size(); i++) {
documentBundles.add(documents.get(i).getBundle());
}
try {
mService.putDocuments(mPackageName, mDatabaseName, documentBundles, mUserHandle,
/*binderCallStartTimeMillis=*/ SystemClock.elapsedRealtime(),
new IAppSearchBatchResultCallback.Stub() {
@Override
public void onResult(AppSearchBatchResultParcel resultParcel) {
executor.execute(() -> callback.onResult(resultParcel.getResult()));
}
@Override
public void onSystemError(AppSearchResultParcel resultParcel) {
executor.execute(() -> sendSystemErrorToCallback(
resultParcel.getResult(), callback));
}
});
mIsMutated = true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Gets {@link GenericDocument} objects by document IDs in a namespace from the {@link
* AppSearchSession} database.
*
* @param request a request containing a namespace and IDs to get documents for.
* @param executor Executor on which to invoke the callback.
* @param callback Callback to receive the pending result of performing this operation. The keys
* of the returned {@link AppSearchBatchResult} are the input IDs. The values
* are the returned {@link GenericDocument}s on success, or a failed
* {@link AppSearchResult} otherwise. IDs that are not found will return a
* failed {@link AppSearchResult} with a result code of
* {@link AppSearchResult#RESULT_NOT_FOUND}. If an unexpected internal error
* occurs in the AppSearch service, {@link BatchResultCallback#onSystemError}
* will be invoked with a {@link Throwable}.
*/
public void getByDocumentId(
@NonNull GetByDocumentIdRequest request,
@NonNull @CallbackExecutor Executor executor,
@NonNull BatchResultCallback<String, GenericDocument> callback) {
Objects.requireNonNull(request);
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed");
try {
mService.getDocuments(
mPackageName,
mDatabaseName,
request.getNamespace(),
new ArrayList<>(request.getIds()),
request.getProjectionsInternal(),
mUserHandle,
/*binderCallStartTimeMillis=*/ SystemClock.elapsedRealtime(),
new IAppSearchBatchResultCallback.Stub() {
@Override
public void onResult(AppSearchBatchResultParcel resultParcel) {
executor.execute(() -> {
AppSearchBatchResult<String, Bundle> result =
resultParcel.getResult();
AppSearchBatchResult.Builder<String, GenericDocument>
documentResultBuilder =
new AppSearchBatchResult.Builder<>();
// Translate successful results
for (Map.Entry<String, Bundle> bundleEntry :
result.getSuccesses().entrySet()) {
GenericDocument document;
try {
document = new GenericDocument(bundleEntry.getValue());
} catch (Throwable t) {
// These documents went through validation, so how could
// this fail? We must have done something wrong.
documentResultBuilder.setFailure(
bundleEntry.getKey(),
AppSearchResult.RESULT_INTERNAL_ERROR,
t.getMessage());
continue;
}
documentResultBuilder.setSuccess(
bundleEntry.getKey(), document);
}
// Translate failed results
for (Map.Entry<String, AppSearchResult<Bundle>> bundleEntry :
((Map<String, AppSearchResult<Bundle>>)
result.getFailures()).entrySet()) {
documentResultBuilder.setFailure(
bundleEntry.getKey(),
bundleEntry.getValue().getResultCode(),
bundleEntry.getValue().getErrorMessage());
}
callback.onResult(documentResultBuilder.build());
});
}
@Override
public void onSystemError(AppSearchResultParcel result) {
executor.execute(
() -> sendSystemErrorToCallback(result.getResult(), callback));
}
});
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Retrieves documents from the open {@link AppSearchSession} that match a given query
* string and type of search provided.
*
* <p>Query strings can be empty, contain one term with no operators, or contain multiple terms
* and operators.
*
* <p>For query strings that are empty, all documents that match the {@link SearchSpec} will be
* returned.
*
* <p>For query strings with a single term and no operators, documents that match the provided
* query string and {@link SearchSpec} will be returned.
*
* <p>The following operators are supported:
*
* <ul>
* <li>AND (implicit)
* <p>AND is an operator that matches documents that contain <i>all</i> provided terms.
* <p><b>NOTE:</b> A space between terms is treated as an "AND" operator. Explicitly
* including "AND" in a query string will treat "AND" as a term, returning documents that
* also contain "AND".
* <p>Example: "apple AND banana" matches documents that contain the terms "apple", "and",
* "banana".
* <p>Example: "apple banana" matches documents that contain both "apple" and "banana".
* <p>Example: "apple banana cherry" matches documents that contain "apple", "banana", and
* "cherry".
* <li>OR
* <p>OR is an operator that matches documents that contain <i>any</i> provided term.
* <p>Example: "apple OR banana" matches documents that contain either "apple" or
* "banana".
* <p>Example: "apple OR banana OR cherry" matches documents that contain any of "apple",
* "banana", or "cherry".
* <li>Exclusion (-)
* <p>Exclusion (-) is an operator that matches documents that <i>do not</i> contain the
* provided term.
* <p>Example: "-apple" matches documents that do not contain "apple".
* <li>Grouped Terms
* <p>For queries that require multiple operators and terms, terms can be grouped into
* subqueries. Subqueries are contained within an open "(" and close ")" parenthesis.
* <p>Example: "(donut OR bagel) (coffee OR tea)" matches documents that contain either
* "donut" or "bagel" and either "coffee" or "tea".
* <li>Property Restricts
* <p>For queries that require a term to match a specific {@link AppSearchSchema} property
* of a document, a ":" must be included between the property name and the term.
* <p>Example: "subject:important" matches documents that contain the term "important" in
* the "subject" property.
* </ul>
*
* <p>Additional search specifications, such as filtering by {@link AppSearchSchema} type or
* adding projection, can be set by calling the corresponding {@link SearchSpec.Builder} setter.
*
* <p>This method is lightweight. The heavy work will be done in {@link
* SearchResults#getNextPage}.
*
* @param queryExpression query string to search.
* @param searchSpec spec for setting document filters, adding projection, setting term match
* type, etc.
* @return a {@link SearchResults} object for retrieved matched documents.
*/
@NonNull
public SearchResults search(@NonNull String queryExpression, @NonNull SearchSpec searchSpec) {
Objects.requireNonNull(queryExpression);
Objects.requireNonNull(searchSpec);
Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed");
return new SearchResults(mService, mPackageName, mDatabaseName, queryExpression,
searchSpec, mUserHandle);
}
/**
* Reports usage of a particular document by namespace and ID.
*
* <p>A usage report represents an event in which a user interacted with or viewed a document.
*
* <p>For each call to {@link #reportUsage}, AppSearch updates usage count and usage recency
* metrics for that particular document. These metrics are used for ordering {@link #search}
* results by the {@link SearchSpec#RANKING_STRATEGY_USAGE_COUNT} and {@link
* SearchSpec#RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP} ranking strategies.
*
* <p>Reporting usage of a document is optional.
*
* @param request The usage reporting request.
* @param executor Executor on which to invoke the callback.
* @param callback Callback to receive errors. If the operation succeeds, the callback will be
* invoked with {@code null}.
*/
public void reportUsage(
@NonNull ReportUsageRequest request,
@NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<AppSearchResult<Void>> callback) {
Objects.requireNonNull(request);
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed");
try {
mService.reportUsage(
mPackageName,
mDatabaseName,
request.getNamespace(),
request.getDocumentId(),
request.getUsageTimestampMillis(),
/*systemUsage=*/ false,
mUserHandle,
new IAppSearchResultCallback.Stub() {
@Override
public void onResult(AppSearchResultParcel resultParcel) {
executor.execute(() -> callback.accept(resultParcel.getResult()));
}
});
mIsMutated = true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Removes {@link GenericDocument} objects by document IDs in a namespace from the {@link
* AppSearchSession} database.
*
* <p>Removed documents will no longer be surfaced by {@link #search} or {@link
* #getByDocumentId} calls.
*
* <p>Once the database crosses the document count or byte usage threshold, removed documents
* will be deleted from disk.
*
* @param request {@link RemoveByDocumentIdRequest} with IDs in a namespace to remove from the
* index.
* @param executor Executor on which to invoke the callback.
* @param callback Callback to receive the pending result of performing this operation. The keys
* of the returned {@link AppSearchBatchResult} are the input document IDs. The
* values are {@code null} on success, or a failed {@link AppSearchResult}
* otherwise. IDs that are not found will return a failed
* {@link AppSearchResult} with a result code of
* {@link AppSearchResult#RESULT_NOT_FOUND}. If an unexpected internal error
* occurs in the AppSearch service, {@link BatchResultCallback#onSystemError}
* will be invoked with a {@link Throwable}.
*/
public void remove(
@NonNull RemoveByDocumentIdRequest request,
@NonNull @CallbackExecutor Executor executor,
@NonNull BatchResultCallback<String, Void> callback) {
Objects.requireNonNull(request);
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed");
try {
mService.removeByDocumentId(
mPackageName,
mDatabaseName,
request.getNamespace(),
new ArrayList<>(request.getIds()),
mUserHandle,
/*binderCallStartTimeMillis=*/ SystemClock.elapsedRealtime(),
new IAppSearchBatchResultCallback.Stub() {
@Override
public void onResult(AppSearchBatchResultParcel resultParcel) {
executor.execute(() -> callback.onResult(resultParcel.getResult()));
}
@Override
public void onSystemError(AppSearchResultParcel resultParcel) {
executor.execute(() -> sendSystemErrorToCallback(
resultParcel.getResult(), callback));
}
});
mIsMutated = true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Removes {@link GenericDocument}s from the index by Query. Documents will be removed if they
* match the {@code queryExpression} in given namespaces and schemaTypes which is set via {@link
* SearchSpec.Builder#addFilterNamespaces} and {@link SearchSpec.Builder#addFilterSchemas}.
*
* <p>An empty {@code queryExpression} matches all documents.
*
* <p>An empty set of namespaces or schemaTypes matches all namespaces or schemaTypes in the
* current database.
*
* @param queryExpression Query String to search.
* @param searchSpec Spec containing schemaTypes, namespaces and query expression indicates how
* document will be removed. All specific about how to scoring, ordering, snippeting and
* resulting will be ignored.
* @param executor Executor on which to invoke the callback.
* @param callback Callback to receive errors resulting from removing the documents. If
* the operation succeeds, the callback will be invoked with
* {@code null}.
*/
public void remove(
@NonNull String queryExpression,
@NonNull SearchSpec searchSpec,
@NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<AppSearchResult<Void>> callback) {
Objects.requireNonNull(queryExpression);
Objects.requireNonNull(searchSpec);
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed");
try {
mService.removeByQuery(
mPackageName,
mDatabaseName,
queryExpression,
searchSpec.getBundle(),
mUserHandle,
/*binderCallStartTimeMillis=*/ SystemClock.elapsedRealtime(),
new IAppSearchResultCallback.Stub() {
@Override
public void onResult(AppSearchResultParcel resultParcel) {
executor.execute(() -> callback.accept(resultParcel.getResult()));
}
});
mIsMutated = true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Gets the storage info for this {@link AppSearchSession} database.
*
* <p>This may take time proportional to the number of documents and may be inefficient to call
* repeatedly.
*
* @param executor Executor on which to invoke the callback.
* @param callback Callback to receive the storage info.
*/
public void getStorageInfo(
@NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<AppSearchResult<StorageInfo>> callback) {
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed");
try {
mService.getStorageInfo(
mPackageName,
mDatabaseName,
mUserHandle,
new IAppSearchResultCallback.Stub() {
@Override
public void onResult(AppSearchResultParcel resultParcel) {
executor.execute(() -> {
AppSearchResult<Bundle> result = resultParcel.getResult();
if (result.isSuccess()) {
StorageInfo response = new StorageInfo(result.getResultValue());
callback.accept(AppSearchResult.newSuccessfulResult(response));
} else {
callback.accept(AppSearchResult.newFailedResult(result));
}
});
}
});
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Closes the {@link AppSearchSession} to persist all schema and document updates,
* additions, and deletes to disk.
*/
@Override
public void close() {
if (mIsMutated && !mIsClosed) {
try {
mService.persistToDisk(
mPackageName,
mUserHandle,
/*binderCallStartTimeMillis=*/ SystemClock.elapsedRealtime());
mIsClosed = true;
} catch (RemoteException e) {
Log.e(TAG, "Unable to close the AppSearchSession", e);
}
}
}
/**
* Set schema to Icing for no-migration scenario.
*
* <p>We only need one time {@link #setSchema} call for no-migration scenario by using the
* forceoverride in the request.
*/
private void setSchemaNoMigrations(
@NonNull SetSchemaRequest request,
@NonNull List<Bundle> schemaBundles,
@NonNull Map<String, List<Bundle>> schemasVisibleToPackagesBundles,
@NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<AppSearchResult<SetSchemaResponse>> callback) {
try {
mService.setSchema(
mPackageName,
mDatabaseName,
schemaBundles,
new ArrayList<>(request.getSchemasNotDisplayedBySystem()),
schemasVisibleToPackagesBundles,
request.isForceOverride(),
request.getVersion(),
mUserHandle,
/*binderCallStartTimeMillis=*/ SystemClock.elapsedRealtime(),
new IAppSearchResultCallback.Stub() {
@Override
public void onResult(AppSearchResultParcel resultParcel) {
executor.execute(() -> {
AppSearchResult<Bundle> result = resultParcel.getResult();
if (result.isSuccess()) {
try {
SetSchemaResponse setSchemaResponse =
new SetSchemaResponse(result.getResultValue());
if (!request.isForceOverride()) {
// Throw exception if there is any deleted types or
// incompatible types. That's the only case we swallowed
// in the AppSearchImpl#setSchema().
SchemaMigrationUtil.checkDeletedAndIncompatible(
setSchemaResponse.getDeletedTypes(),
setSchemaResponse.getIncompatibleTypes());
}
callback.accept(AppSearchResult
.newSuccessfulResult(setSchemaResponse));
} catch (Throwable t) {
callback.accept(AppSearchResult.throwableToFailedResult(t));
}
} else {
callback.accept(AppSearchResult.newFailedResult(result));
}
});
}
});
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Set schema to Icing for migration scenario.
*
* <p>First time {@link #setSchema} call with forceOverride is false gives us all incompatible
* changes. After trigger migrations, the second time call {@link #setSchema} will actually
* apply the changes.
*/
private void setSchemaWithMigrations(
@NonNull SetSchemaRequest request,
@NonNull List<Bundle> schemaBundles,
@NonNull Map<String, List<Bundle>> schemasVisibleToPackagesBundles,
@NonNull Executor workExecutor,
@NonNull @CallbackExecutor Executor callbackExecutor,
@NonNull Consumer<AppSearchResult<SetSchemaResponse>> callback) {
workExecutor.execute(() -> {
try {
// Migration process
// 1. Validate and retrieve all active migrators.
CompletableFuture<AppSearchResult<GetSchemaResponse>> getSchemaFuture =
new CompletableFuture<>();
getSchema(callbackExecutor, getSchemaFuture::complete);
AppSearchResult<GetSchemaResponse> getSchemaResult = getSchemaFuture.get();
if (!getSchemaResult.isSuccess()) {
callbackExecutor.execute(() ->
callback.accept(AppSearchResult.newFailedResult(getSchemaResult)));
return;
}
GetSchemaResponse getSchemaResponse = getSchemaResult.getResultValue();
int currentVersion = getSchemaResponse.getVersion();
int finalVersion = request.getVersion();
Map<String, Migrator> activeMigrators = SchemaMigrationUtil.getActiveMigrators(
getSchemaResponse.getSchemas(), request.getMigrators(), currentVersion,
finalVersion);
// No need to trigger migration if no migrator is active.
if (activeMigrators.isEmpty()) {
setSchemaNoMigrations(request, schemaBundles, schemasVisibleToPackagesBundles,
callbackExecutor, callback);
return;
}
// 2. SetSchema with forceOverride=false, to retrieve the list of
// incompatible/deleted types.
CompletableFuture<AppSearchResult<Bundle>> setSchemaFuture =
new CompletableFuture<>();
mService.setSchema(
mPackageName,
mDatabaseName,
schemaBundles,
new ArrayList<>(request.getSchemasNotDisplayedBySystem()),
schemasVisibleToPackagesBundles,
/*forceOverride=*/ false,
request.getVersion(),
mUserHandle,
/*binderCallStartTimeMillis=*/ SystemClock.elapsedRealtime(),
new IAppSearchResultCallback.Stub() {
@Override
public void onResult(AppSearchResultParcel resultParcel) {
setSchemaFuture.complete(resultParcel.getResult());
}
});
AppSearchResult<Bundle> setSchemaResult = setSchemaFuture.get();
if (!setSchemaResult.isSuccess()) {
callbackExecutor.execute(() ->
callback.accept(AppSearchResult.newFailedResult(setSchemaResult)));
return;
}
SetSchemaResponse setSchemaResponse =
new SetSchemaResponse(setSchemaResult.getResultValue());
// 3. If forceOverride is false, check that all incompatible types will be migrated.
// If some aren't we must throw an error, rather than proceeding and deleting those
// types.
if (!request.isForceOverride()) {
SchemaMigrationUtil.checkDeletedAndIncompatibleAfterMigration(setSchemaResponse,
activeMigrators.keySet());
}
try (AppSearchMigrationHelper migrationHelper = new AppSearchMigrationHelper(
mService, mUserHandle, mPackageName, mDatabaseName, request.getSchemas())) {
// 4. Trigger migration for all migrators.
// TODO(b/177266929) trigger migration for all types together rather than
// separately.
for (Map.Entry<String, Migrator> entry : activeMigrators.entrySet()) {
migrationHelper.queryAndTransform(/*schemaType=*/ entry.getKey(),
/*migrator=*/ entry.getValue(), currentVersion,
finalVersion);
}
// 5. SetSchema a second time with forceOverride=true if the first attempted
// failed.
if (!setSchemaResponse.getIncompatibleTypes().isEmpty()
|| !setSchemaResponse.getDeletedTypes().isEmpty()) {
CompletableFuture<AppSearchResult<Bundle>> setSchema2Future =
new CompletableFuture<>();
// only trigger second setSchema() call if the first one is fail.
mService.setSchema(
mPackageName,
mDatabaseName,
schemaBundles,
new ArrayList<>(request.getSchemasNotDisplayedBySystem()),
schemasVisibleToPackagesBundles,
/*forceOverride=*/ true,
request.getVersion(),
mUserHandle,
/*binderCallStartTimeMillis=*/ SystemClock.elapsedRealtime(),
new IAppSearchResultCallback.Stub() {
@Override
public void onResult(AppSearchResultParcel resultParcel) {
setSchema2Future.complete(resultParcel.getResult());
}
});
AppSearchResult<Bundle> setSchema2Result = setSchema2Future.get();
if (!setSchema2Result.isSuccess()) {
// we failed to set the schema in second time with forceOverride = true,
// which is an impossible case. Since we only swallow the incompatible
// error in the first setSchema call, all other errors will be thrown at
// the first time.
callbackExecutor.execute(() -> callback.accept(
AppSearchResult.newFailedResult(setSchema2Result)));
return;
}
}
SetSchemaResponse.Builder responseBuilder = setSchemaResponse.toBuilder()
.addMigratedTypes(activeMigrators.keySet());
// 6. Put all the migrated documents into the index, now that the new schema is
// set.
AppSearchResult<SetSchemaResponse> putResult =
migrationHelper.putMigratedDocuments(responseBuilder);
callbackExecutor.execute(() -> callback.accept(putResult));
}
} catch (Throwable t) {
callbackExecutor.execute(() -> callback.accept(
AppSearchResult.throwableToFailedResult(t)));
}
});
}
/**
* Calls {@link BatchResultCallback#onSystemError} with a throwable derived from the given
* failed {@link AppSearchResult}.
*
* <p>The {@link AppSearchResult} generally comes from
* {@link IAppSearchBatchResultCallback#onSystemError}.
*
* <p>This method should be called from the callback executor thread.
*/
private void sendSystemErrorToCallback(
@NonNull AppSearchResult<?> failedResult, @NonNull BatchResultCallback<?, ?> callback) {
Preconditions.checkArgument(!failedResult.isSuccess());
Throwable throwable = new AppSearchException(
failedResult.getResultCode(), failedResult.getErrorMessage());
callback.onSystemError(throwable);
}
}

View File

@@ -1,58 +0,0 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch;
import android.annotation.NonNull;
import android.annotation.Nullable;
/**
* The callback interface to return {@link AppSearchBatchResult}.
*
* @param <KeyType> The type of the keys for {@link AppSearchBatchResult#getSuccesses} and
* {@link AppSearchBatchResult#getFailures}.
* @param <ValueType> The type of result objects associated with the keys.
*/
public interface BatchResultCallback<KeyType, ValueType> {
/**
* Called when {@link AppSearchBatchResult} results are ready.
*
* @param result The result of the executed request.
*/
void onResult(@NonNull AppSearchBatchResult<KeyType, ValueType> result);
/**
* Called when a system error occurs.
*
* <p>This method is only called the infrastructure is fundamentally broken or unavailable, such
* that none of the requests could be started. For example, it will be called if the AppSearch
* service unexpectedly fails to initialize and can't be recovered by any means, or if
* communicating to the server over Binder fails (e.g. system service crashed or device is
* rebooting).
*
* <p>The error is not expected to be recoverable and there is no specific recommended action
* other than displaying a permanent message to the user.
*
* <p>Normal errors that are caused by invalid inputs or recoverable/retriable situations
* are reported associated with the input that caused them via the {@link #onResult} method.
*
* @param throwable an exception describing the system error
*/
default void onSystemError(@Nullable Throwable throwable) {
throw new RuntimeException("Unrecoverable system error", throwable);
}
}

View File

@@ -1,200 +0,0 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.app.appsearch.aidl.AppSearchResultParcel;
import android.app.appsearch.aidl.IAppSearchManager;
import android.app.appsearch.aidl.IAppSearchResultCallback;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Log;
import com.android.internal.util.Preconditions;
import java.io.Closeable;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
* Provides a connection to all AppSearch databases the querying application has been granted access
* to.
*
* <p>This class is thread safe.
*
* @see AppSearchSession
*/
public class GlobalSearchSession implements Closeable {
private static final String TAG = "AppSearchGlobalSearchSe";
private final String mPackageName;
private final UserHandle mUserHandle;
private final IAppSearchManager mService;
private boolean mIsMutated = false;
private boolean mIsClosed = false;
/**
* Creates a search session for the client, defined by the {@code userHandle} and
* {@code packageName}.
*/
static void createGlobalSearchSession(
@NonNull IAppSearchManager service,
@NonNull UserHandle userHandle,
@NonNull String packageName,
@NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<AppSearchResult<GlobalSearchSession>> callback) {
GlobalSearchSession globalSearchSession = new GlobalSearchSession(service, userHandle,
packageName);
globalSearchSession.initialize(executor, callback);
}
// NOTE: No instance of this class should be created or returned except via initialize().
// Once the callback.accept has been called here, the class is ready to use.
private void initialize(
@NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<AppSearchResult<GlobalSearchSession>> callback) {
try {
mService.initialize(
mPackageName,
mUserHandle,
/*binderCallStartTimeMillis=*/ SystemClock.elapsedRealtime(),
new IAppSearchResultCallback.Stub() {
@Override
public void onResult(AppSearchResultParcel resultParcel) {
executor.execute(() -> {
AppSearchResult<Void> result = resultParcel.getResult();
if (result.isSuccess()) {
callback.accept(
AppSearchResult.newSuccessfulResult(
GlobalSearchSession.this));
} else {
callback.accept(AppSearchResult.newFailedResult(result));
}
});
}
});
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
private GlobalSearchSession(@NonNull IAppSearchManager service, @NonNull UserHandle userHandle,
@NonNull String packageName) {
mService = service;
mUserHandle = userHandle;
mPackageName = packageName;
}
/**
* Retrieves documents from all AppSearch databases that the querying application has access to.
*
* <p>Applications can be granted access to documents by specifying {@link
* SetSchemaRequest.Builder#setSchemaTypeVisibilityForPackage} when building a schema.
*
* <p>Document access can also be granted to system UIs by specifying {@link
* SetSchemaRequest.Builder#setSchemaTypeDisplayedBySystem} when building a schema.
*
* <p>See {@link AppSearchSession#search} for a detailed explanation on forming a query string.
*
* <p>This method is lightweight. The heavy work will be done in {@link
* SearchResults#getNextPage}.
*
* @param queryExpression query string to search.
* @param searchSpec spec for setting document filters, adding projection, setting term match
* type, etc.
* @return a {@link SearchResults} object for retrieved matched documents.
*/
@NonNull
public SearchResults search(@NonNull String queryExpression, @NonNull SearchSpec searchSpec) {
Objects.requireNonNull(queryExpression);
Objects.requireNonNull(searchSpec);
Preconditions.checkState(!mIsClosed, "GlobalSearchSession has already been closed");
return new SearchResults(mService, mPackageName, /*databaseName=*/null, queryExpression,
searchSpec, mUserHandle);
}
/**
* Reports that a particular document has been used from a system surface.
*
* <p>See {@link AppSearchSession#reportUsage} for a general description of document usage, as
* well as an API that can be used by the app itself.
*
* <p>Usage reported via this method is accounted separately from usage reported via
* {@link AppSearchSession#reportUsage} and may be accessed using the constants
* {@link SearchSpec#RANKING_STRATEGY_SYSTEM_USAGE_COUNT} and
* {@link SearchSpec#RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP}.
*
* @param request The usage reporting request.
* @param executor Executor on which to invoke the callback.
* @param callback Callback to receive errors. If the operation succeeds, the callback will be
* invoked with an {@link AppSearchResult} whose value is {@code null}. The
* callback will be invoked with an {@link AppSearchResult} of
* {@link AppSearchResult#RESULT_SECURITY_ERROR} if this API is invoked by an
* app which is not part of the system.
*/
public void reportSystemUsage(
@NonNull ReportSystemUsageRequest request,
@NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<AppSearchResult<Void>> callback) {
Objects.requireNonNull(request);
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
Preconditions.checkState(!mIsClosed, "GlobalSearchSession has already been closed");
try {
mService.reportUsage(
request.getPackageName(),
request.getDatabaseName(),
request.getNamespace(),
request.getDocumentId(),
request.getUsageTimestampMillis(),
/*systemUsage=*/ true,
mUserHandle,
new IAppSearchResultCallback.Stub() {
@Override
public void onResult(AppSearchResultParcel resultParcel) {
executor.execute(() -> callback.accept(resultParcel.getResult()));
}
});
mIsMutated = true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Closes the {@link GlobalSearchSession}. Persists all mutations, including usage reports, to
* disk.
*/
@Override
public void close() {
if (mIsMutated && !mIsClosed) {
try {
mService.persistToDisk(
mPackageName,
mUserHandle,
/*binderCallStartTimeMillis=*/ SystemClock.elapsedRealtime());
mIsClosed = true;
} catch (RemoteException e) {
Log.e(TAG, "Unable to close the GlobalSearchSession", e);
}
}
}
}

View File

@@ -1,175 +0,0 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.appsearch.aidl.AppSearchResultParcel;
import android.app.appsearch.aidl.IAppSearchManager;
import android.app.appsearch.aidl.IAppSearchResultCallback;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Log;
import com.android.internal.util.Preconditions;
import java.io.Closeable;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
* Encapsulates results of a search operation.
*
* <p>Each {@link AppSearchSession#search} operation returns a list of {@link SearchResult} objects,
* referred to as a "page", limited by the size configured by {@link
* SearchSpec.Builder#setResultCountPerPage}.
*
* <p>To fetch a page of results, call {@link #getNextPage}.
*
* <p>All instances of {@link SearchResults} must call {@link SearchResults#close()} after the
* results are fetched.
*
* <p>This class is not thread safe.
*/
public class SearchResults implements Closeable {
private static final String TAG = "SearchResults";
private final IAppSearchManager mService;
// The package name of the caller.
private final String mPackageName;
// The database name to search over. If null, this will search over all database names.
@Nullable
private final String mDatabaseName;
private final String mQueryExpression;
private final SearchSpec mSearchSpec;
private final UserHandle mUserHandle;
private long mNextPageToken;
private boolean mIsFirstLoad = true;
private boolean mIsClosed = false;
SearchResults(
@NonNull IAppSearchManager service,
@NonNull String packageName,
@Nullable String databaseName,
@NonNull String queryExpression,
@NonNull SearchSpec searchSpec,
@NonNull UserHandle userHandle) {
mService = Objects.requireNonNull(service);
mPackageName = packageName;
mDatabaseName = databaseName;
mQueryExpression = Objects.requireNonNull(queryExpression);
mSearchSpec = Objects.requireNonNull(searchSpec);
mUserHandle = Objects.requireNonNull(userHandle);
}
/**
* Retrieves the next page of {@link SearchResult} objects.
*
* <p>The page size is configured by {@link SearchSpec.Builder#setResultCountPerPage}.
*
* <p>Continue calling this method to access results until it returns an empty list, signifying
* there are no more results.
*
* @param executor Executor on which to invoke the callback.
* @param callback Callback to receive the pending result of performing this operation.
*/
public void getNextPage(
@NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<AppSearchResult<List<SearchResult>>> callback) {
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
Preconditions.checkState(!mIsClosed, "SearchResults has already been closed");
try {
if (mIsFirstLoad) {
mIsFirstLoad = false;
long binderCallStartTimeMillis = SystemClock.elapsedRealtime();
if (mDatabaseName == null) {
// Global query, there's no one package-database combination to check.
mService.globalQuery(mPackageName, mQueryExpression,
mSearchSpec.getBundle(), mUserHandle,
binderCallStartTimeMillis,
wrapCallback(executor, callback));
} else {
// Normal local query, pass in specified database.
mService.query(mPackageName, mDatabaseName, mQueryExpression,
mSearchSpec.getBundle(), mUserHandle,
binderCallStartTimeMillis,
wrapCallback(executor, callback));
}
} else {
mService.getNextPage(mPackageName, mNextPageToken, mUserHandle,
wrapCallback(executor, callback));
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
@Override
public void close() {
if (!mIsClosed) {
try {
mService.invalidateNextPageToken(mPackageName, mNextPageToken, mUserHandle);
mIsClosed = true;
} catch (RemoteException e) {
Log.e(TAG, "Unable to close the SearchResults", e);
}
}
}
private IAppSearchResultCallback wrapCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<AppSearchResult<List<SearchResult>>> callback) {
return new IAppSearchResultCallback.Stub() {
@Override
public void onResult(AppSearchResultParcel resultParcel) {
executor.execute(() -> invokeCallback(resultParcel.getResult(), callback));
}
};
}
private void invokeCallback(
@NonNull AppSearchResult<Bundle> searchResultPageResult,
@NonNull Consumer<AppSearchResult<List<SearchResult>>> callback) {
if (searchResultPageResult.isSuccess()) {
try {
SearchResultPage searchResultPage =
new SearchResultPage(searchResultPageResult.getResultValue());
mNextPageToken = searchResultPage.getNextPageToken();
callback.accept(AppSearchResult.newSuccessfulResult(
searchResultPage.getResults()));
} catch (Throwable t) {
callback.accept(AppSearchResult.throwableToFailedResult(t));
}
} else {
callback.accept(AppSearchResult.newFailedResult(searchResultPageResult));
}
}
}

View File

@@ -1,19 +0,0 @@
/**
* Copyright 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 android.app.appsearch.aidl;
/** {@hide} */
parcelable AppSearchBatchResultParcel<ValueType>;

View File

@@ -1,97 +0,0 @@
/*
* 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 android.app.appsearch.aidl;
import android.annotation.NonNull;
import android.app.appsearch.AppSearchBatchResult;
import android.app.appsearch.AppSearchResult;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Map;
import java.util.Objects;
/**
* Parcelable wrapper around {@link AppSearchBatchResult}.
*
* <p>{@link AppSearchBatchResult} can contain any type of key and value, including non-parcelable
* values. For the specific case of sending {@link AppSearchBatchResult} across Binder, this class
* wraps an {@link AppSearchBatchResult} that has String keys and Parcelable values. It provides
* parcelability of the whole structure.
*
* @param <ValueType> The type of result object for successful calls. Must be a parcelable type.
* @hide
*/
public final class AppSearchBatchResultParcel<ValueType> implements Parcelable {
private final AppSearchBatchResult<String, ValueType> mResult;
/** Creates a new {@link AppSearchBatchResultParcel} from the given result. */
public AppSearchBatchResultParcel(@NonNull AppSearchBatchResult<String, ValueType> result) {
mResult = Objects.requireNonNull(result);
}
private AppSearchBatchResultParcel(@NonNull Parcel in) {
Bundle bundle = in.readBundle();
AppSearchBatchResult.Builder<String, ValueType> builder =
new AppSearchBatchResult.Builder<>();
for (String key : bundle.keySet()) {
AppSearchResultParcel<ValueType> resultParcel = bundle.getParcelable(key);
builder.setResult(key, resultParcel.getResult());
}
mResult = builder.build();
}
@NonNull
public AppSearchBatchResult<String, ValueType> getResult() {
return mResult;
}
/** @hide */
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
Bundle bundle = new Bundle();
for (Map.Entry<String, AppSearchResult<ValueType>> entry
: mResult.getAll().entrySet()) {
bundle.putParcelable(entry.getKey(), new AppSearchResultParcel<>(entry.getValue()));
}
dest.writeBundle(bundle);
}
/** @hide */
@Override
public int describeContents() {
return 0;
}
/** @hide */
@NonNull
public static final Creator<AppSearchBatchResultParcel<?>> CREATOR =
new Creator<AppSearchBatchResultParcel<?>>() {
@NonNull
@Override
public AppSearchBatchResultParcel<?> createFromParcel(@NonNull Parcel in) {
return new AppSearchBatchResultParcel<>(in);
}
@NonNull
@Override
public AppSearchBatchResultParcel<?>[] newArray(int size) {
return new AppSearchBatchResultParcel<?>[size];
}
};
}

View File

@@ -1,19 +0,0 @@
/**
* Copyright 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 android.app.appsearch.aidl;
/** {@hide} */
parcelable AppSearchResultParcel<ValueType>;

View File

@@ -1,95 +0,0 @@
/*
* 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 android.app.appsearch.aidl;
import android.annotation.NonNull;
import android.app.appsearch.AppSearchResult;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Objects;
/**
* Parcelable wrapper around {@link AppSearchResult}.
*
* <p>{@link AppSearchResult} can contain any value, including non-parcelable values. For the
* specific case of sending {@link AppSearchResult} across Binder, this class wraps an
* {@link AppSearchResult} that contains a parcelable type and provides parcelability of the whole
* structure.
*
* @param <ValueType> The type of result object for successful calls. Must be a parcelable type.
* @hide
*/
public final class AppSearchResultParcel<ValueType> implements Parcelable {
private final AppSearchResult<ValueType> mResult;
/** Creates a new {@link AppSearchResultParcel} from the given result. */
public AppSearchResultParcel(@NonNull AppSearchResult<ValueType> result) {
mResult = Objects.requireNonNull(result);
}
private AppSearchResultParcel(@NonNull Parcel in) {
int resultCode = in.readInt();
ValueType resultValue = (ValueType) in.readValue(/*loader=*/ null);
String errorMessage = in.readString();
if (resultCode == AppSearchResult.RESULT_OK) {
mResult = AppSearchResult.newSuccessfulResult(resultValue);
} else {
mResult = AppSearchResult.newFailedResult(resultCode, errorMessage);
}
}
@NonNull
public AppSearchResult<ValueType> getResult() {
return mResult;
}
/** @hide */
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mResult.getResultCode());
if (mResult.isSuccess()) {
dest.writeValue(mResult.getResultValue());
} else {
dest.writeValue(null);
}
dest.writeString(mResult.getErrorMessage());
}
/** @hide */
@Override
public int describeContents() {
return 0;
}
/** @hide */
@NonNull
public static final Creator<AppSearchResultParcel<?>> CREATOR =
new Creator<AppSearchResultParcel<?>>() {
@NonNull
@Override
public AppSearchResultParcel<?> createFromParcel(@NonNull Parcel in) {
return new AppSearchResultParcel<>(in);
}
@NonNull
@Override
public AppSearchResultParcel<?>[] newArray(int size) {
return new AppSearchResultParcel<?>[size];
}
};
}

View File

@@ -1,25 +0,0 @@
/**
* Copyright 2020, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch.aidl;
import android.app.appsearch.aidl.AppSearchBatchResultParcel;
import android.app.appsearch.aidl.AppSearchResultParcel;
/** {@hide} */
oneway interface IAppSearchBatchResultCallback {
void onResult(in AppSearchBatchResultParcel resultParcel);
void onSystemError(in AppSearchResultParcel resultParcel);
}

View File

@@ -1,371 +0,0 @@
/**
* Copyright 2020, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch.aidl;
import android.os.Bundle;
import android.os.UserHandle;
import android.app.appsearch.aidl.IAppSearchBatchResultCallback;
import android.app.appsearch.aidl.IAppSearchResultCallback;
import android.os.ParcelFileDescriptor;
/** {@hide} */
interface IAppSearchManager {
/**
* Updates the AppSearch schema for this database.
*
* @param packageName The name of the package that owns this schema.
* @param databaseName The name of the database where this schema lives.
* @param schemaBundles List of {@link AppSearchSchema} bundles.
* @param schemasNotDisplayedBySystem Schema types that should not be surfaced on platform
* surfaces.
* @param schemasVisibleToPackagesBundles Schema types that are visible to the specified
* packages. The value List contains PackageIdentifier Bundles.
* @param forceOverride Whether to apply the new schema even if it is incompatible. All
* incompatible documents will be deleted.
* @param schemaVersion The overall schema version number of the request.
* @param userHandle Handle of the calling user
* @param binderCallStartTimeMillis start timestamp of binder call in Millis
* @param callback {@link IAppSearchResultCallback#onResult} will be called with an
* {@link AppSearchResult}&lt;{@link Bundle}&gt;, where the value are
* {@link SetSchemaResponse} bundle.
*/
void setSchema(
in String packageName,
in String databaseName,
in List<Bundle> schemaBundles,
in List<String> schemasNotDisplayedBySystem,
in Map<String, List<Bundle>> schemasVisibleToPackagesBundles,
boolean forceOverride,
in int schemaVersion,
in UserHandle userHandle,
in long binderCallStartTimeMillis,
in IAppSearchResultCallback callback);
/**
* Retrieves the AppSearch schema for this database.
*
* @param packageName The name of the package that owns the schema.
* @param databaseName The name of the database to retrieve.
* @param userHandle Handle of the calling user
* @param callback {@link IAppSearchResultCallback#onResult} will be called with an
* {@link AppSearchResult}&lt;{@link Bundle}&gt; where the bundle is a GetSchemaResponse.
*/
void getSchema(
in String packageName,
in String databaseName,
in UserHandle userHandle,
in IAppSearchResultCallback callback);
/**
* Retrieves the set of all namespaces in the current database with at least one document.
*
* @param packageName The name of the package that owns the schema.
* @param databaseName The name of the database to retrieve.
* @param userHandle Handle of the calling user
* @param callback {@link IAppSearchResultCallback#onResult} will be called with an
* {@link AppSearchResult}&lt;{@link List}&lt;{@link String}&gt;&gt;.
*/
void getNamespaces(
in String packageName,
in String databaseName,
in UserHandle userHandle,
in IAppSearchResultCallback callback);
/**
* Inserts documents into the index.
*
* @param packageName The name of the package that owns this document.
* @param databaseName The name of the database where this document lives.
* @param documentBundes List of GenericDocument bundles.
* @param userHandle Handle of the calling user
* @param binderCallStartTimeMillis start timestamp of binder call in Millis
* @param callback
* If the call fails to start, {@link IAppSearchBatchResultCallback#onSystemError}
* will be called with the cause throwable. Otherwise,
* {@link IAppSearchBatchResultCallback#onResult} will be called with an
* {@link AppSearchBatchResult}&lt;{@link String}, {@link Void}&gt;
* where the keys are document IDs, and the values are {@code null}.
*/
void putDocuments(
in String packageName,
in String databaseName,
in List<Bundle> documentBundles,
in UserHandle userHandle,
in long binderCallStartTimeMillis,
in IAppSearchBatchResultCallback callback);
/**
* Retrieves documents from the index.
*
* @param packageName The name of the package that owns this document.
* @param databaseName The databaseName this document resides in.
* @param namespace The namespace this document resides in.
* @param ids The IDs of the documents to retrieve
* @param typePropertyPaths A map of schema type to a list of property paths to return in the
* result.
* @param userHandle Handle of the calling user
* @param binderCallStartTimeMillis start timestamp of binder call in Millis
* @param callback
* If the call fails to start, {@link IAppSearchBatchResultCallback#onSystemError}
* will be called with the cause throwable. Otherwise,
* {@link IAppSearchBatchResultCallback#onResult} will be called with an
* {@link AppSearchBatchResult}&lt;{@link String}, {@link Bundle}&gt;
* where the keys are document IDs, and the values are Document bundles.
*/
void getDocuments(
in String packageName,
in String databaseName,
in String namespace,
in List<String> ids,
in Map<String, List<String>> typePropertyPaths,
in UserHandle userHandle,
in long binderCallStartTimeMillis,
in IAppSearchBatchResultCallback callback);
/**
* Searches a document based on a given specifications.
*
* @param packageName The name of the package to query over.
* @param databaseName The databaseName this query for.
* @param queryExpression String to search for
* @param searchSpecBundle SearchSpec bundle
* @param userHandle Handle of the calling user
* @param binderCallStartTimeMillis start timestamp of binder call in Millis
* @param callback {@link AppSearchResult}&lt;{@link Bundle}&gt; of performing this
* operation.
*/
void query(
in String packageName,
in String databaseName,
in String queryExpression,
in Bundle searchSpecBundle,
in UserHandle userHandle,
in long binderCallStartTimeMillis,
in IAppSearchResultCallback callback);
/**
* Executes a global query, i.e. over all permitted databases, against the AppSearch index and
* returns results.
*
* @param packageName The name of the package making the query.
* @param queryExpression String to search for
* @param searchSpecBundle SearchSpec bundle
* @param userHandle Handle of the calling user
* @param binderCallStartTimeMillis start timestamp of binder call in Millis
* @param callback {@link AppSearchResult}&lt;{@link Bundle}&gt; of performing this
* operation.
*/
void globalQuery(
in String packageName,
in String queryExpression,
in Bundle searchSpecBundle,
in UserHandle userHandle,
in long binderCallStartTimeMillis,
in IAppSearchResultCallback callback);
/**
* Fetches the next page of results of a previously executed query. Results can be empty if
* next-page token is invalid or all pages have been returned.
*
* @param packageName The name of the package to persist to disk for.
* @param nextPageToken The token of pre-loaded results of previously executed query.
* @param userHandle Handle of the calling user
* @param callback {@link AppSearchResult}&lt;{@link Bundle}&gt; of performing this
* operation.
*/
void getNextPage(
in String packageName,
in long nextPageToken,
in UserHandle userHandle,
in IAppSearchResultCallback callback);
/**
* Invalidates the next-page token so that no more results of the related query can be returned.
*
* @param packageName The name of the package to persist to disk for.
* @param nextPageToken The token of pre-loaded results of previously executed query to be
* Invalidated.
* @param userHandle Handle of the calling user
*/
void invalidateNextPageToken(
in String packageName,
in long nextPageToken,
in UserHandle userHandle);
/**
* Searches a document based on a given specifications.
*
* <p>Documents will be save to the given ParcelFileDescriptor
*
* @param packageName The name of the package to query over.
* @param databaseName The databaseName this query for.
* @param fileDescriptor The ParcelFileDescriptor where documents should be written to.
* @param queryExpression String to search for.
* @param searchSpecBundle SearchSpec bundle.
* @param userHandle Handle of the calling user.
* @param callback {@link IAppSearchResultCallback#onResult} will be called with an
* {@link AppSearchResult}&lt;{@code Void}&gt;.
*/
void writeQueryResultsToFile(
in String packageName,
in String databaseName,
in ParcelFileDescriptor fileDescriptor,
in String queryExpression,
in Bundle searchSpecBundle,
in UserHandle userHandle,
in IAppSearchResultCallback callback);
/**
* Inserts documents from the given file into the index.
*
* @param packageName The name of the package that owns this document.
* @param databaseName The name of the database where this document lives.
* @param fileDescriptor The ParcelFileDescriptor where documents should be read from.
* @param userHandle Handle of the calling user.
* @param callback {@link IAppSearchResultCallback#onResult} will be called with an
* {@link AppSearchResult}&lt;{@link List}&lt;{@link Bundle}&gt;&gt;, where the value are
* MigrationFailure bundles.
*/
void putDocumentsFromFile(
in String packageName,
in String databaseName,
in ParcelFileDescriptor fileDescriptor,
in UserHandle userHandle,
in IAppSearchResultCallback callback);
/**
* Reports usage of a particular document by namespace and id.
*
* <p>A usage report represents an event in which a user interacted with or viewed a document.
*
* <p>For each call to {@link #reportUsage}, AppSearch updates usage count and usage recency
* metrics for that particular document. These metrics are used for ordering {@link #query}
* results by the {@link SearchSpec#RANKING_STRATEGY_USAGE_COUNT} and
* {@link SearchSpec#RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP} ranking strategies.
*
* <p>Reporting usage of a document is optional.
*
* @param packageName The name of the package that owns this document.
* @param databaseName The name of the database to report usage against.
* @param namespace Namespace the document being used belongs to.
* @param id ID of the document being used.
* @param usageTimestampMillis The timestamp at which the document was used.
* @param systemUsage Whether the usage was reported by a system app against another app's doc.
* @param userHandle Handle of the calling user
* @param callback {@link IAppSearchResultCallback#onResult} will be called with an
* {@link AppSearchResult}&lt;{@link Void}&gt;.
*/
void reportUsage(
in String packageName,
in String databaseName,
in String namespace,
in String id,
in long usageTimestampMillis,
in boolean systemUsage,
in UserHandle userHandle,
in IAppSearchResultCallback callback);
/**
* Removes documents by ID.
*
* @param packageName The name of the package the document is in.
* @param databaseName The databaseName the document is in.
* @param namespace Namespace of the document to remove.
* @param ids The IDs of the documents to delete
* @param userHandle Handle of the calling user
* @param binderCallStartTimeMillis start timestamp of binder call in Millis
* @param callback
* If the call fails to start, {@link IAppSearchBatchResultCallback#onSystemError}
* will be called with the cause throwable. Otherwise,
* {@link IAppSearchBatchResultCallback#onResult} will be called with an
* {@link AppSearchBatchResult}&lt;{@link String}, {@link Void}&gt;
* where the keys are document IDs. If a document doesn't exist, it will be reported as a
* failure where the {@code throwable} is {@code null}.
*/
void removeByDocumentId(
in String packageName,
in String databaseName,
in String namespace,
in List<String> ids,
in UserHandle userHandle,
in long binderCallStartTimeMillis,
in IAppSearchBatchResultCallback callback);
/**
* Removes documents by given query.
*
* @param packageName The name of the package to query over.
* @param databaseName The databaseName this query for.
* @param queryExpression String to search for
* @param searchSpecBundle SearchSpec bundle
* @param userHandle Handle of the calling user
* @param binderCallStartTimeMillis start timestamp of binder call in Millis
* @param callback {@link IAppSearchResultCallback#onResult} will be called with an
* {@link AppSearchResult}&lt;{@link Void}&gt;.
*/
void removeByQuery(
in String packageName,
in String databaseName,
in String queryExpression,
in Bundle searchSpecBundle,
in UserHandle userHandle,
in long binderCallStartTimeMillis,
in IAppSearchResultCallback callback);
/**
* Gets the storage info.
*
* @param packageName The name of the package to get the storage info for.
* @param databaseName The databaseName to get the storage info for.
* @param userHandle Handle of the calling user
* @param callback {@link IAppSearchResultCallback#onResult} will be called with an
* {@link AppSearchResult}&lt;{@link Bundle}&gt;, where the value is a
* {@link StorageInfo}.
*/
void getStorageInfo(
in String packageName,
in String databaseName,
in UserHandle userHandle,
in IAppSearchResultCallback callback);
/**
* Persists all update/delete requests to the disk.
*
* @param packageName The name of the package to persist to disk for.
* @param userHandle Handle of the calling user
* @param binderCallStartTimeMillis start timestamp of binder call in Millis
*/
void persistToDisk(
in String packageName,
in UserHandle userHandle,
in long binderCallStartTimeMillis);
/**
* Creates and initializes AppSearchImpl for the calling app.
*
* @param packageName The name of the package to initialize for.
* @param userHandle Handle of the calling user
* @param binderCallStartTimeMillis start timestamp of binder call in Millis
* @param callback {@link IAppSearchResultCallback#onResult} will be called with an
* {@link AppSearchResult}&lt;{@link Void}&gt;.
*/
void initialize(
in String packageName,
in UserHandle userHandle,
in long binderCallStartTimeMillis,
in IAppSearchResultCallback callback);
}

View File

@@ -1,23 +0,0 @@
/**
* Copyright 2020, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch.aidl;
import android.app.appsearch.aidl.AppSearchResultParcel;
/** {@hide} */
oneway interface IAppSearchResultCallback {
void onResult(in AppSearchResultParcel resultParcel);
}

View File

@@ -1,209 +0,0 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.ArrayMap;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
/**
* Provides results for AppSearch batch operations which encompass multiple documents.
*
* <p>Individual results of a batch operation are separated into two maps: one for successes and one
* for failures. For successes, {@link #getSuccesses()} will return a map of keys to instances of
* the value type. For failures, {@link #getFailures()} will return a map of keys to {@link
* AppSearchResult} objects.
*
* <p>Alternatively, {@link #getAll()} returns a map of keys to {@link AppSearchResult} objects for
* both successes and failures.
*
* @param <KeyType> The type of the keys for which the results will be reported.
* @param <ValueType> The type of the result objects for successful results.
* @see AppSearchSession#put
* @see AppSearchSession#getByDocumentId
* @see AppSearchSession#remove
*/
public final class AppSearchBatchResult<KeyType, ValueType> {
@NonNull private final Map<KeyType, ValueType> mSuccesses;
@NonNull private final Map<KeyType, AppSearchResult<ValueType>> mFailures;
@NonNull private final Map<KeyType, AppSearchResult<ValueType>> mAll;
AppSearchBatchResult(
@NonNull Map<KeyType, ValueType> successes,
@NonNull Map<KeyType, AppSearchResult<ValueType>> failures,
@NonNull Map<KeyType, AppSearchResult<ValueType>> all) {
mSuccesses = Objects.requireNonNull(successes);
mFailures = Objects.requireNonNull(failures);
mAll = Objects.requireNonNull(all);
}
/** Returns {@code true} if this {@link AppSearchBatchResult} has no failures. */
public boolean isSuccess() {
return mFailures.isEmpty();
}
/**
* Returns a {@link Map} of keys mapped to instances of the value type for all successful
* individual results.
*
* <p>Example: {@link AppSearchSession#getByDocumentId} returns an {@link AppSearchBatchResult}.
* Each key (the document ID, of {@code String} type) will map to a {@link GenericDocument}
* object.
*
* <p>The values of the {@link Map} will not be {@code null}.
*/
@NonNull
public Map<KeyType, ValueType> getSuccesses() {
return Collections.unmodifiableMap(mSuccesses);
}
/**
* Returns a {@link Map} of keys mapped to instances of {@link AppSearchResult} for all failed
* individual results.
*
* <p>The values of the {@link Map} will not be {@code null}.
*/
@NonNull
public Map<KeyType, AppSearchResult<ValueType>> getFailures() {
return Collections.unmodifiableMap(mFailures);
}
/**
* Returns a {@link Map} of keys mapped to instances of {@link AppSearchResult} for all
* individual results.
*
* <p>The values of the {@link Map} will not be {@code null}.
*/
@NonNull
public Map<KeyType, AppSearchResult<ValueType>> getAll() {
return Collections.unmodifiableMap(mAll);
}
@Override
@NonNull
public String toString() {
return "{\n successes: " + mSuccesses + "\n failures: " + mFailures + "\n}";
}
/**
* Builder for {@link AppSearchBatchResult} objects.
*
* @param <KeyType> The type of the keys for which the results will be reported.
* @param <ValueType> The type of the result objects for successful results.
*/
public static final class Builder<KeyType, ValueType> {
private ArrayMap<KeyType, ValueType> mSuccesses = new ArrayMap<>();
private ArrayMap<KeyType, AppSearchResult<ValueType>> mFailures = new ArrayMap<>();
private ArrayMap<KeyType, AppSearchResult<ValueType>> mAll = new ArrayMap<>();
private boolean mBuilt = false;
/**
* Associates the {@code key} with the provided successful return value.
*
* <p>Any previous mapping for a key, whether success or failure, is deleted.
*
* <p>This is a convenience function which is equivalent to {@code setResult(key,
* AppSearchResult.newSuccessfulResult(value))}.
*
* @param key The key to associate the result with; usually corresponds to some identifier
* from the input like an ID or name.
* @param value An optional value to associate with the successful result of the operation
* being performed.
*/
@SuppressWarnings("MissingGetterMatchingBuilder") // See getSuccesses
@NonNull
public Builder<KeyType, ValueType> setSuccess(
@NonNull KeyType key, @Nullable ValueType value) {
Objects.requireNonNull(key);
resetIfBuilt();
return setResult(key, AppSearchResult.newSuccessfulResult(value));
}
/**
* Associates the {@code key} with the provided failure code and error message.
*
* <p>Any previous mapping for a key, whether success or failure, is deleted.
*
* <p>This is a convenience function which is equivalent to {@code setResult(key,
* AppSearchResult.newFailedResult(resultCode, errorMessage))}.
*
* @param key The key to associate the result with; usually corresponds to some identifier
* from the input like an ID or name.
* @param resultCode One of the constants documented in {@link
* AppSearchResult#getResultCode}.
* @param errorMessage An optional string describing the reason or nature of the failure.
*/
@SuppressWarnings("MissingGetterMatchingBuilder") // See getFailures
@NonNull
public Builder<KeyType, ValueType> setFailure(
@NonNull KeyType key,
@AppSearchResult.ResultCode int resultCode,
@Nullable String errorMessage) {
Objects.requireNonNull(key);
resetIfBuilt();
return setResult(key, AppSearchResult.newFailedResult(resultCode, errorMessage));
}
/**
* Associates the {@code key} with the provided {@code result}.
*
* <p>Any previous mapping for a key, whether success or failure, is deleted.
*
* @param key The key to associate the result with; usually corresponds to some identifier
* from the input like an ID or name.
* @param result The result to associate with the key.
*/
@SuppressWarnings("MissingGetterMatchingBuilder") // See getAll
@NonNull
public Builder<KeyType, ValueType> setResult(
@NonNull KeyType key, @NonNull AppSearchResult<ValueType> result) {
Objects.requireNonNull(key);
Objects.requireNonNull(result);
resetIfBuilt();
if (result.isSuccess()) {
mSuccesses.put(key, result.getResultValue());
mFailures.remove(key);
} else {
mFailures.put(key, result);
mSuccesses.remove(key);
}
mAll.put(key, result);
return this;
}
/**
* Builds an {@link AppSearchBatchResult} object from the contents of this {@link Builder}.
*/
@NonNull
public AppSearchBatchResult<KeyType, ValueType> build() {
mBuilt = true;
return new AppSearchBatchResult<>(mSuccesses, mFailures, mAll);
}
private void resetIfBuilt() {
if (mBuilt) {
mSuccesses = new ArrayMap<>(mSuccesses);
mFailures = new ArrayMap<>(mFailures);
mAll = new ArrayMap<>(mAll);
mBuilt = false;
}
}
}
}

View File

@@ -1,249 +0,0 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.appsearch.exceptions.AppSearchException;
import android.util.Log;
import com.android.internal.util.Preconditions;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/**
* Information about the success or failure of an AppSearch call.
*
* @param <ValueType> The type of result object for successful calls.
*/
public final class AppSearchResult<ValueType> {
private static final String TAG = "AppSearchResult";
/**
* Result codes from {@link AppSearchSession} methods.
*
* @hide
*/
@IntDef(
value = {
RESULT_OK,
RESULT_UNKNOWN_ERROR,
RESULT_INTERNAL_ERROR,
RESULT_INVALID_ARGUMENT,
RESULT_IO_ERROR,
RESULT_OUT_OF_SPACE,
RESULT_NOT_FOUND,
RESULT_INVALID_SCHEMA,
RESULT_SECURITY_ERROR,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ResultCode {}
/** The call was successful. */
public static final int RESULT_OK = 0;
/** An unknown error occurred while processing the call. */
public static final int RESULT_UNKNOWN_ERROR = 1;
/**
* An internal error occurred within AppSearch, which the caller cannot address.
*
* <p>This error may be considered similar to {@link IllegalStateException}
*/
public static final int RESULT_INTERNAL_ERROR = 2;
/**
* The caller supplied invalid arguments to the call.
*
* <p>This error may be considered similar to {@link IllegalArgumentException}.
*/
public static final int RESULT_INVALID_ARGUMENT = 3;
/**
* An issue occurred reading or writing to storage. The call might succeed if repeated.
*
* <p>This error may be considered similar to {@link java.io.IOException}.
*/
public static final int RESULT_IO_ERROR = 4;
/** Storage is out of space, and no more space could be reclaimed. */
public static final int RESULT_OUT_OF_SPACE = 5;
/** An entity the caller requested to interact with does not exist in the system. */
public static final int RESULT_NOT_FOUND = 6;
/** The caller supplied a schema which is invalid or incompatible with the previous schema. */
public static final int RESULT_INVALID_SCHEMA = 7;
/** The caller requested an operation it does not have privileges for. */
public static final int RESULT_SECURITY_ERROR = 8;
private final @ResultCode int mResultCode;
@Nullable private final ValueType mResultValue;
@Nullable private final String mErrorMessage;
private AppSearchResult(
@ResultCode int resultCode,
@Nullable ValueType resultValue,
@Nullable String errorMessage) {
mResultCode = resultCode;
mResultValue = resultValue;
mErrorMessage = errorMessage;
}
/** Returns {@code true} if {@link #getResultCode} equals {@link AppSearchResult#RESULT_OK}. */
public boolean isSuccess() {
return getResultCode() == RESULT_OK;
}
/** Returns one of the {@code RESULT} constants defined in {@link AppSearchResult}. */
public @ResultCode int getResultCode() {
return mResultCode;
}
/**
* Returns the result value associated with this result, if it was successful.
*
* <p>See the documentation of the particular {@link AppSearchSession} call producing this
* {@link AppSearchResult} for what is placed in the result value by that call.
*
* @throws IllegalStateException if this {@link AppSearchResult} is not successful.
*/
@Nullable
public ValueType getResultValue() {
if (!isSuccess()) {
throw new IllegalStateException("AppSearchResult is a failure: " + this);
}
return mResultValue;
}
/**
* Returns the error message associated with this result.
*
* <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}. The error
* message may be {@code null} even if {@link #isSuccess} is {@code false}. See the
* documentation of the particular {@link AppSearchSession} call producing this {@link
* AppSearchResult} for what is returned by {@link #getErrorMessage}.
*/
@Nullable
public String getErrorMessage() {
return mErrorMessage;
}
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof AppSearchResult)) {
return false;
}
AppSearchResult<?> otherResult = (AppSearchResult<?>) other;
return mResultCode == otherResult.mResultCode
&& Objects.equals(mResultValue, otherResult.mResultValue)
&& Objects.equals(mErrorMessage, otherResult.mErrorMessage);
}
@Override
public int hashCode() {
return Objects.hash(mResultCode, mResultValue, mErrorMessage);
}
@Override
@NonNull
public String toString() {
if (isSuccess()) {
return "[SUCCESS]: " + mResultValue;
}
return "[FAILURE(" + mResultCode + ")]: " + mErrorMessage;
}
/**
* Creates a new successful {@link AppSearchResult}.
*
* @param value An optional value to associate with the successful result of the operation being
* performed.
*/
@NonNull
public static <ValueType> AppSearchResult<ValueType> newSuccessfulResult(
@Nullable ValueType value) {
return new AppSearchResult<>(RESULT_OK, value, /*errorMessage=*/ null);
}
/**
* Creates a new failed {@link AppSearchResult}.
*
* @param resultCode One of the constants documented in {@link AppSearchResult#getResultCode}.
* @param errorMessage An optional string describing the reason or nature of the failure.
*/
@NonNull
public static <ValueType> AppSearchResult<ValueType> newFailedResult(
@ResultCode int resultCode, @Nullable String errorMessage) {
return new AppSearchResult<>(resultCode, /*resultValue=*/ null, errorMessage);
}
/**
* Creates a new failed {@link AppSearchResult} by a AppSearchResult in another type.
*
* @hide
*/
@NonNull
public static <ValueType> AppSearchResult<ValueType> newFailedResult(
@NonNull AppSearchResult<?> otherFailedResult) {
Preconditions.checkState(
!otherFailedResult.isSuccess(),
"Cannot convert a success result to a failed result");
return AppSearchResult.newFailedResult(
otherFailedResult.getResultCode(), otherFailedResult.getErrorMessage());
}
/** @hide */
@NonNull
public static <ValueType> AppSearchResult<ValueType> throwableToFailedResult(
@NonNull Throwable t) {
// Log for traceability. NOT_FOUND is logged at VERBOSE because this error can occur during
// the regular operation of the system (b/183550974). Everything else is logged at DEBUG.
if (t instanceof AppSearchException
&& ((AppSearchException) t).getResultCode() == RESULT_NOT_FOUND) {
Log.v(TAG, "Converting throwable to failed result: " + t);
} else {
Log.d(TAG, "Converting throwable to failed result.", t);
}
if (t instanceof AppSearchException) {
return ((AppSearchException) t).toAppSearchResult();
}
String exceptionClass = t.getClass().getSimpleName();
@AppSearchResult.ResultCode int resultCode;
if (t instanceof IllegalStateException || t instanceof NullPointerException) {
resultCode = AppSearchResult.RESULT_INTERNAL_ERROR;
} else if (t instanceof IllegalArgumentException) {
resultCode = AppSearchResult.RESULT_INVALID_ARGUMENT;
} else if (t instanceof IOException) {
resultCode = AppSearchResult.RESULT_IO_ERROR;
} else if (t instanceof SecurityException) {
resultCode = AppSearchResult.RESULT_SECURITY_ERROR;
} else {
resultCode = AppSearchResult.RESULT_UNKNOWN_ERROR;
}
return AppSearchResult.newFailedResult(resultCode, exceptionClass + ": " + t.getMessage());
}
}

View File

@@ -1,922 +0,0 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.appsearch.exceptions.IllegalSchemaException;
import android.app.appsearch.util.BundleUtil;
import android.app.appsearch.util.IndentingStringBuilder;
import android.os.Bundle;
import android.util.ArraySet;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
/**
* The AppSearch Schema for a particular type of document.
*
* <p>For example, an e-mail message or a music recording could be a schema type.
*
* <p>The schema consists of type information, properties, and config (like tokenization type).
*
* @see AppSearchSession#setSchema
*/
public final class AppSearchSchema {
private static final String SCHEMA_TYPE_FIELD = "schemaType";
private static final String PROPERTIES_FIELD = "properties";
private final Bundle mBundle;
/** @hide */
public AppSearchSchema(@NonNull Bundle bundle) {
Objects.requireNonNull(bundle);
mBundle = bundle;
}
/**
* Returns the {@link Bundle} populated by this builder.
*
* @hide
*/
@NonNull
public Bundle getBundle() {
return mBundle;
}
@Override
@NonNull
public String toString() {
IndentingStringBuilder stringBuilder = new IndentingStringBuilder();
appendAppSearchSchemaString(stringBuilder);
return stringBuilder.toString();
}
/**
* Appends a debugging string for the {@link AppSearchSchema} instance to the given string
* builder.
*
* @param builder the builder to append to.
*/
private void appendAppSearchSchemaString(@NonNull IndentingStringBuilder builder) {
Objects.requireNonNull(builder);
builder.append("{\n");
builder.increaseIndentLevel();
builder.append("schemaType: \"").append(getSchemaType()).append("\",\n");
builder.append("properties: [\n");
AppSearchSchema.PropertyConfig[] sortedProperties =
getProperties().toArray(new AppSearchSchema.PropertyConfig[0]);
Arrays.sort(sortedProperties, (o1, o2) -> o1.getName().compareTo(o2.getName()));
for (int i = 0; i < sortedProperties.length; i++) {
AppSearchSchema.PropertyConfig propertyConfig = sortedProperties[i];
builder.increaseIndentLevel();
propertyConfig.appendPropertyConfigString(builder);
if (i != sortedProperties.length - 1) {
builder.append(",\n");
}
builder.decreaseIndentLevel();
}
builder.append("\n");
builder.append("]\n");
builder.decreaseIndentLevel();
builder.append("}");
}
/** Returns the name of this schema type, e.g. Email. */
@NonNull
public String getSchemaType() {
return mBundle.getString(SCHEMA_TYPE_FIELD, "");
}
/**
* Returns the list of {@link PropertyConfig}s that are part of this schema.
*
* <p>This method creates a new list when called.
*/
@NonNull
@SuppressWarnings("MixedMutabilityReturnType")
public List<PropertyConfig> getProperties() {
ArrayList<Bundle> propertyBundles =
mBundle.getParcelableArrayList(AppSearchSchema.PROPERTIES_FIELD);
if (propertyBundles.isEmpty()) {
return Collections.emptyList();
}
List<PropertyConfig> ret = new ArrayList<>(propertyBundles.size());
for (int i = 0; i < propertyBundles.size(); i++) {
ret.add(PropertyConfig.fromBundle(propertyBundles.get(i)));
}
return ret;
}
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof AppSearchSchema)) {
return false;
}
AppSearchSchema otherSchema = (AppSearchSchema) other;
if (!getSchemaType().equals(otherSchema.getSchemaType())) {
return false;
}
return getProperties().equals(otherSchema.getProperties());
}
@Override
public int hashCode() {
return Objects.hash(getSchemaType(), getProperties());
}
/** Builder for {@link AppSearchSchema objects}. */
public static final class Builder {
private final String mSchemaType;
private ArrayList<Bundle> mPropertyBundles = new ArrayList<>();
private final Set<String> mPropertyNames = new ArraySet<>();
private boolean mBuilt = false;
/** Creates a new {@link AppSearchSchema.Builder}. */
public Builder(@NonNull String schemaType) {
Objects.requireNonNull(schemaType);
mSchemaType = schemaType;
}
/** Adds a property to the given type. */
@NonNull
public AppSearchSchema.Builder addProperty(@NonNull PropertyConfig propertyConfig) {
Objects.requireNonNull(propertyConfig);
resetIfBuilt();
String name = propertyConfig.getName();
if (!mPropertyNames.add(name)) {
throw new IllegalSchemaException("Property defined more than once: " + name);
}
mPropertyBundles.add(propertyConfig.mBundle);
return this;
}
/** Constructs a new {@link AppSearchSchema} from the contents of this builder. */
@NonNull
public AppSearchSchema build() {
Bundle bundle = new Bundle();
bundle.putString(AppSearchSchema.SCHEMA_TYPE_FIELD, mSchemaType);
bundle.putParcelableArrayList(AppSearchSchema.PROPERTIES_FIELD, mPropertyBundles);
mBuilt = true;
return new AppSearchSchema(bundle);
}
private void resetIfBuilt() {
if (mBuilt) {
mPropertyBundles = new ArrayList<>(mPropertyBundles);
mBuilt = false;
}
}
}
/**
* Common configuration for a single property (field) in a Document.
*
* <p>For example, an {@code EmailMessage} would be a type and the {@code subject} would be a
* property.
*/
public abstract static class PropertyConfig {
static final String NAME_FIELD = "name";
static final String DATA_TYPE_FIELD = "dataType";
static final String CARDINALITY_FIELD = "cardinality";
/**
* Physical data-types of the contents of the property.
*
* @hide
*/
// NOTE: The integer values of these constants must match the proto enum constants in
// com.google.android.icing.proto.PropertyConfigProto.DataType.Code.
@IntDef(
value = {
DATA_TYPE_STRING,
DATA_TYPE_LONG,
DATA_TYPE_DOUBLE,
DATA_TYPE_BOOLEAN,
DATA_TYPE_BYTES,
DATA_TYPE_DOCUMENT,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DataType {}
/** @hide */
public static final int DATA_TYPE_STRING = 1;
/** @hide */
public static final int DATA_TYPE_LONG = 2;
/** @hide */
public static final int DATA_TYPE_DOUBLE = 3;
/** @hide */
public static final int DATA_TYPE_BOOLEAN = 4;
/**
* Unstructured BLOB.
*
* @hide
*/
public static final int DATA_TYPE_BYTES = 5;
/**
* Indicates that the property is itself a {@link GenericDocument}, making it part of a
* hierarchical schema. Any property using this DataType MUST have a valid {@link
* PropertyConfig#getSchemaType}.
*
* @hide
*/
public static final int DATA_TYPE_DOCUMENT = 6;
/**
* The cardinality of the property (whether it is required, optional or repeated).
*
* @hide
*/
// NOTE: The integer values of these constants must match the proto enum constants in
// com.google.android.icing.proto.PropertyConfigProto.Cardinality.Code.
@IntDef(
value = {
CARDINALITY_REPEATED,
CARDINALITY_OPTIONAL,
CARDINALITY_REQUIRED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Cardinality {}
/** Any number of items (including zero) [0...*]. */
public static final int CARDINALITY_REPEATED = 1;
/** Zero or one value [0,1]. */
public static final int CARDINALITY_OPTIONAL = 2;
/** Exactly one value [1]. */
public static final int CARDINALITY_REQUIRED = 3;
final Bundle mBundle;
@Nullable private Integer mHashCode;
PropertyConfig(@NonNull Bundle bundle) {
mBundle = Objects.requireNonNull(bundle);
}
@Override
@NonNull
public String toString() {
IndentingStringBuilder stringBuilder = new IndentingStringBuilder();
appendPropertyConfigString(stringBuilder);
return stringBuilder.toString();
}
/**
* Appends a debug string for the {@link AppSearchSchema.PropertyConfig} instance to the
* given string builder.
*
* @param builder the builder to append to.
*/
void appendPropertyConfigString(@NonNull IndentingStringBuilder builder) {
Objects.requireNonNull(builder);
builder.append("{\n");
builder.increaseIndentLevel();
builder.append("name: \"").append(getName()).append("\",\n");
if (this instanceof AppSearchSchema.StringPropertyConfig) {
((StringPropertyConfig) this).appendStringPropertyConfigFields(builder);
} else if (this instanceof AppSearchSchema.DocumentPropertyConfig) {
((DocumentPropertyConfig) this).appendDocumentPropertyConfigFields(builder);
}
switch (getCardinality()) {
case AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED:
builder.append("cardinality: CARDINALITY_REPEATED,\n");
break;
case AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL:
builder.append("cardinality: CARDINALITY_OPTIONAL,\n");
break;
case AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED:
builder.append("cardinality: CARDINALITY_REQUIRED,\n");
break;
default:
builder.append("cardinality: CARDINALITY_UNKNOWN,\n");
}
switch (getDataType()) {
case AppSearchSchema.PropertyConfig.DATA_TYPE_STRING:
builder.append("dataType: DATA_TYPE_STRING,\n");
break;
case AppSearchSchema.PropertyConfig.DATA_TYPE_LONG:
builder.append("dataType: DATA_TYPE_LONG,\n");
break;
case AppSearchSchema.PropertyConfig.DATA_TYPE_DOUBLE:
builder.append("dataType: DATA_TYPE_DOUBLE,\n");
break;
case AppSearchSchema.PropertyConfig.DATA_TYPE_BOOLEAN:
builder.append("dataType: DATA_TYPE_BOOLEAN,\n");
break;
case AppSearchSchema.PropertyConfig.DATA_TYPE_BYTES:
builder.append("dataType: DATA_TYPE_BYTES,\n");
break;
case AppSearchSchema.PropertyConfig.DATA_TYPE_DOCUMENT:
builder.append("dataType: DATA_TYPE_DOCUMENT,\n");
break;
default:
builder.append("dataType: DATA_TYPE_UNKNOWN,\n");
}
builder.decreaseIndentLevel();
builder.append("}");
}
/** Returns the name of this property. */
@NonNull
public String getName() {
return mBundle.getString(NAME_FIELD, "");
}
/**
* Returns the type of data the property contains (e.g. string, int, bytes, etc).
*
* @hide
*/
public @DataType int getDataType() {
return mBundle.getInt(DATA_TYPE_FIELD, -1);
}
/**
* Returns the cardinality of the property (whether it is optional, required or repeated).
*/
public @Cardinality int getCardinality() {
return mBundle.getInt(CARDINALITY_FIELD, CARDINALITY_OPTIONAL);
}
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof PropertyConfig)) {
return false;
}
PropertyConfig otherProperty = (PropertyConfig) other;
return BundleUtil.deepEquals(this.mBundle, otherProperty.mBundle);
}
@Override
public int hashCode() {
if (mHashCode == null) {
mHashCode = BundleUtil.deepHashCode(mBundle);
}
return mHashCode;
}
/**
* Converts a {@link Bundle} into a {@link PropertyConfig} depending on its internal data
* type.
*
* <p>The bundle is not cloned.
*
* @throws IllegalArgumentException if the bundle does no contain a recognized value in its
* {@code DATA_TYPE_FIELD}.
* @hide
*/
@NonNull
public static PropertyConfig fromBundle(@NonNull Bundle propertyBundle) {
switch (propertyBundle.getInt(PropertyConfig.DATA_TYPE_FIELD)) {
case PropertyConfig.DATA_TYPE_STRING:
return new StringPropertyConfig(propertyBundle);
case PropertyConfig.DATA_TYPE_LONG:
return new LongPropertyConfig(propertyBundle);
case PropertyConfig.DATA_TYPE_DOUBLE:
return new DoublePropertyConfig(propertyBundle);
case PropertyConfig.DATA_TYPE_BOOLEAN:
return new BooleanPropertyConfig(propertyBundle);
case PropertyConfig.DATA_TYPE_BYTES:
return new BytesPropertyConfig(propertyBundle);
case PropertyConfig.DATA_TYPE_DOCUMENT:
return new DocumentPropertyConfig(propertyBundle);
default:
throw new IllegalArgumentException(
"Unsupported property bundle of type "
+ propertyBundle.getInt(PropertyConfig.DATA_TYPE_FIELD)
+ "; contents: "
+ propertyBundle);
}
}
}
/** Configuration for a property of type String in a Document. */
public static final class StringPropertyConfig extends PropertyConfig {
private static final String INDEXING_TYPE_FIELD = "indexingType";
private static final String TOKENIZER_TYPE_FIELD = "tokenizerType";
/**
* Encapsulates the configurations on how AppSearch should query/index these terms.
*
* @hide
*/
@IntDef(
value = {
INDEXING_TYPE_NONE,
INDEXING_TYPE_EXACT_TERMS,
INDEXING_TYPE_PREFIXES,
})
@Retention(RetentionPolicy.SOURCE)
public @interface IndexingType {}
/** Content in this property will not be tokenized or indexed. */
public static final int INDEXING_TYPE_NONE = 0;
/**
* Content in this property should only be returned for queries matching the exact tokens
* appearing in this property.
*
* <p>Ex. A property with "fool" should NOT match a query for "foo".
*/
public static final int INDEXING_TYPE_EXACT_TERMS = 1;
/**
* Content in this property should be returned for queries that are either exact matches or
* query matches of the tokens appearing in this property.
*
* <p>Ex. A property with "fool" <b>should</b> match a query for "foo".
*/
public static final int INDEXING_TYPE_PREFIXES = 2;
/**
* Configures how tokens should be extracted from this property.
*
* @hide
*/
// NOTE: The integer values of these constants must match the proto enum constants in
// com.google.android.icing.proto.IndexingConfig.TokenizerType.Code.
@IntDef(
value = {
TOKENIZER_TYPE_NONE,
TOKENIZER_TYPE_PLAIN,
})
@Retention(RetentionPolicy.SOURCE)
public @interface TokenizerType {}
/**
* This value indicates that no tokens should be extracted from this property.
*
* <p>It is only valid for tokenizer_type to be 'NONE' if {@link #getIndexingType} is {@link
* #INDEXING_TYPE_NONE}.
*/
public static final int TOKENIZER_TYPE_NONE = 0;
/**
* Tokenization for plain text. This value indicates that tokens should be extracted from
* this property based on word breaks. Segments of whitespace and punctuation are not
* considered tokens.
*
* <p>Ex. A property with "foo bar. baz." will produce tokens for "foo", "bar" and "baz".
* The segments " " and "." will not be considered tokens.
*
* <p>It is only valid for tokenizer_type to be 'PLAIN' if {@link #getIndexingType} is
* {@link #INDEXING_TYPE_EXACT_TERMS} or {@link #INDEXING_TYPE_PREFIXES}.
*/
public static final int TOKENIZER_TYPE_PLAIN = 1;
StringPropertyConfig(@NonNull Bundle bundle) {
super(bundle);
}
/** Returns how the property is indexed. */
public @IndexingType int getIndexingType() {
return mBundle.getInt(INDEXING_TYPE_FIELD);
}
/** Returns how this property is tokenized (split into words). */
public @TokenizerType int getTokenizerType() {
return mBundle.getInt(TOKENIZER_TYPE_FIELD);
}
/** Builder for {@link StringPropertyConfig}. */
public static final class Builder {
private final String mPropertyName;
private @Cardinality int mCardinality = CARDINALITY_OPTIONAL;
private @IndexingType int mIndexingType = INDEXING_TYPE_NONE;
private @TokenizerType int mTokenizerType = TOKENIZER_TYPE_NONE;
/** Creates a new {@link StringPropertyConfig.Builder}. */
public Builder(@NonNull String propertyName) {
mPropertyName = Objects.requireNonNull(propertyName);
}
/**
* The cardinality of the property (whether it is optional, required or repeated).
*
* <p>If this method is not called, the default cardinality is {@link
* PropertyConfig#CARDINALITY_OPTIONAL}.
*/
@SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
@NonNull
public StringPropertyConfig.Builder setCardinality(@Cardinality int cardinality) {
Preconditions.checkArgumentInRange(
cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality");
mCardinality = cardinality;
return this;
}
/**
* Configures how a property should be indexed so that it can be retrieved by queries.
*
* <p>If this method is not called, the default indexing type is {@link
* StringPropertyConfig#INDEXING_TYPE_NONE}, so that it cannot be matched by queries.
*/
@NonNull
public StringPropertyConfig.Builder setIndexingType(@IndexingType int indexingType) {
Preconditions.checkArgumentInRange(
indexingType, INDEXING_TYPE_NONE, INDEXING_TYPE_PREFIXES, "indexingType");
mIndexingType = indexingType;
return this;
}
/**
* Configures how this property should be tokenized (split into words).
*
* <p>If this method is not called, the default indexing type is {@link
* StringPropertyConfig#TOKENIZER_TYPE_NONE}, so that it is not tokenized.
*
* <p>This method must be called with a value other than {@link
* StringPropertyConfig#TOKENIZER_TYPE_NONE} if the property is indexed (i.e. if {@link
* #setIndexingType} has been called with a value other than {@link
* StringPropertyConfig#INDEXING_TYPE_NONE}).
*/
@NonNull
public StringPropertyConfig.Builder setTokenizerType(@TokenizerType int tokenizerType) {
Preconditions.checkArgumentInRange(
tokenizerType, TOKENIZER_TYPE_NONE, TOKENIZER_TYPE_PLAIN, "tokenizerType");
mTokenizerType = tokenizerType;
return this;
}
/** Constructs a new {@link StringPropertyConfig} from the contents of this builder. */
@NonNull
public StringPropertyConfig build() {
if (mTokenizerType == TOKENIZER_TYPE_NONE) {
Preconditions.checkState(
mIndexingType == INDEXING_TYPE_NONE,
"Cannot set "
+ "TOKENIZER_TYPE_NONE with an indexing type other than "
+ "INDEXING_TYPE_NONE.");
} else {
Preconditions.checkState(
mIndexingType != INDEXING_TYPE_NONE,
"Cannot set " + "TOKENIZER_TYPE_PLAIN with INDEXING_TYPE_NONE.");
}
Bundle bundle = new Bundle();
bundle.putString(NAME_FIELD, mPropertyName);
bundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_STRING);
bundle.putInt(CARDINALITY_FIELD, mCardinality);
bundle.putInt(INDEXING_TYPE_FIELD, mIndexingType);
bundle.putInt(TOKENIZER_TYPE_FIELD, mTokenizerType);
return new StringPropertyConfig(bundle);
}
}
/**
* Appends a debug string for the {@link StringPropertyConfig} instance to the given string
* builder.
*
* <p>This appends fields specific to a {@link StringPropertyConfig} instance.
*
* @param builder the builder to append to.
*/
void appendStringPropertyConfigFields(@NonNull IndentingStringBuilder builder) {
switch (getIndexingType()) {
case AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE:
builder.append("indexingType: INDEXING_TYPE_NONE,\n");
break;
case AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS:
builder.append("indexingType: INDEXING_TYPE_EXACT_TERMS,\n");
break;
case AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES:
builder.append("indexingType: INDEXING_TYPE_PREFIXES,\n");
break;
default:
builder.append("indexingType: INDEXING_TYPE_UNKNOWN,\n");
}
switch (getTokenizerType()) {
case AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE:
builder.append("tokenizerType: TOKENIZER_TYPE_NONE,\n");
break;
case AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN:
builder.append("tokenizerType: TOKENIZER_TYPE_PLAIN,\n");
break;
default:
builder.append("tokenizerType: TOKENIZER_TYPE_UNKNOWN,\n");
}
}
}
/** Configuration for a property containing a 64-bit integer. */
public static final class LongPropertyConfig extends PropertyConfig {
LongPropertyConfig(@NonNull Bundle bundle) {
super(bundle);
}
/** Builder for {@link LongPropertyConfig}. */
public static final class Builder {
private final String mPropertyName;
private @Cardinality int mCardinality = CARDINALITY_OPTIONAL;
/** Creates a new {@link LongPropertyConfig.Builder}. */
public Builder(@NonNull String propertyName) {
mPropertyName = Objects.requireNonNull(propertyName);
}
/**
* The cardinality of the property (whether it is optional, required or repeated).
*
* <p>If this method is not called, the default cardinality is {@link
* PropertyConfig#CARDINALITY_OPTIONAL}.
*/
@SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
@NonNull
public LongPropertyConfig.Builder setCardinality(@Cardinality int cardinality) {
Preconditions.checkArgumentInRange(
cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality");
mCardinality = cardinality;
return this;
}
/** Constructs a new {@link LongPropertyConfig} from the contents of this builder. */
@NonNull
public LongPropertyConfig build() {
Bundle bundle = new Bundle();
bundle.putString(NAME_FIELD, mPropertyName);
bundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_LONG);
bundle.putInt(CARDINALITY_FIELD, mCardinality);
return new LongPropertyConfig(bundle);
}
}
}
/** Configuration for a property containing a double-precision decimal number. */
public static final class DoublePropertyConfig extends PropertyConfig {
DoublePropertyConfig(@NonNull Bundle bundle) {
super(bundle);
}
/** Builder for {@link DoublePropertyConfig}. */
public static final class Builder {
private final String mPropertyName;
private @Cardinality int mCardinality = CARDINALITY_OPTIONAL;
/** Creates a new {@link DoublePropertyConfig.Builder}. */
public Builder(@NonNull String propertyName) {
mPropertyName = Objects.requireNonNull(propertyName);
}
/**
* The cardinality of the property (whether it is optional, required or repeated).
*
* <p>If this method is not called, the default cardinality is {@link
* PropertyConfig#CARDINALITY_OPTIONAL}.
*/
@SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
@NonNull
public DoublePropertyConfig.Builder setCardinality(@Cardinality int cardinality) {
Preconditions.checkArgumentInRange(
cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality");
mCardinality = cardinality;
return this;
}
/** Constructs a new {@link DoublePropertyConfig} from the contents of this builder. */
@NonNull
public DoublePropertyConfig build() {
Bundle bundle = new Bundle();
bundle.putString(NAME_FIELD, mPropertyName);
bundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_DOUBLE);
bundle.putInt(CARDINALITY_FIELD, mCardinality);
return new DoublePropertyConfig(bundle);
}
}
}
/** Configuration for a property containing a boolean. */
public static final class BooleanPropertyConfig extends PropertyConfig {
BooleanPropertyConfig(@NonNull Bundle bundle) {
super(bundle);
}
/** Builder for {@link BooleanPropertyConfig}. */
public static final class Builder {
private final String mPropertyName;
private @Cardinality int mCardinality = CARDINALITY_OPTIONAL;
/** Creates a new {@link BooleanPropertyConfig.Builder}. */
public Builder(@NonNull String propertyName) {
mPropertyName = Objects.requireNonNull(propertyName);
}
/**
* The cardinality of the property (whether it is optional, required or repeated).
*
* <p>If this method is not called, the default cardinality is {@link
* PropertyConfig#CARDINALITY_OPTIONAL}.
*/
@SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
@NonNull
public BooleanPropertyConfig.Builder setCardinality(@Cardinality int cardinality) {
Preconditions.checkArgumentInRange(
cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality");
mCardinality = cardinality;
return this;
}
/** Constructs a new {@link BooleanPropertyConfig} from the contents of this builder. */
@NonNull
public BooleanPropertyConfig build() {
Bundle bundle = new Bundle();
bundle.putString(NAME_FIELD, mPropertyName);
bundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_BOOLEAN);
bundle.putInt(CARDINALITY_FIELD, mCardinality);
return new BooleanPropertyConfig(bundle);
}
}
}
/** Configuration for a property containing a byte array. */
public static final class BytesPropertyConfig extends PropertyConfig {
BytesPropertyConfig(@NonNull Bundle bundle) {
super(bundle);
}
/** Builder for {@link BytesPropertyConfig}. */
public static final class Builder {
private final String mPropertyName;
private @Cardinality int mCardinality = CARDINALITY_OPTIONAL;
/** Creates a new {@link BytesPropertyConfig.Builder}. */
public Builder(@NonNull String propertyName) {
mPropertyName = Objects.requireNonNull(propertyName);
}
/**
* The cardinality of the property (whether it is optional, required or repeated).
*
* <p>If this method is not called, the default cardinality is {@link
* PropertyConfig#CARDINALITY_OPTIONAL}.
*/
@SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
@NonNull
public BytesPropertyConfig.Builder setCardinality(@Cardinality int cardinality) {
Preconditions.checkArgumentInRange(
cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality");
mCardinality = cardinality;
return this;
}
/** Constructs a new {@link BytesPropertyConfig} from the contents of this builder. */
@NonNull
public BytesPropertyConfig build() {
Bundle bundle = new Bundle();
bundle.putString(NAME_FIELD, mPropertyName);
bundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_BYTES);
bundle.putInt(CARDINALITY_FIELD, mCardinality);
return new BytesPropertyConfig(bundle);
}
}
}
/** Configuration for a property containing another Document. */
public static final class DocumentPropertyConfig extends PropertyConfig {
private static final String SCHEMA_TYPE_FIELD = "schemaType";
private static final String INDEX_NESTED_PROPERTIES_FIELD = "indexNestedProperties";
DocumentPropertyConfig(@NonNull Bundle bundle) {
super(bundle);
}
/** Returns the logical schema-type of the contents of this document property. */
@NonNull
public String getSchemaType() {
return Objects.requireNonNull(mBundle.getString(SCHEMA_TYPE_FIELD));
}
/**
* Returns whether fields in the nested document should be indexed according to that
* document's schema.
*
* <p>If false, the nested document's properties are not indexed regardless of its own
* schema.
*/
public boolean shouldIndexNestedProperties() {
return mBundle.getBoolean(INDEX_NESTED_PROPERTIES_FIELD);
}
/** Builder for {@link DocumentPropertyConfig}. */
public static final class Builder {
private final String mPropertyName;
private final String mSchemaType;
private @Cardinality int mCardinality = CARDINALITY_OPTIONAL;
private boolean mShouldIndexNestedProperties = false;
/**
* Creates a new {@link DocumentPropertyConfig.Builder}.
*
* @param propertyName The logical name of the property in the schema, which will be
* used as the key for this property in {@link
* GenericDocument.Builder#setPropertyDocument}.
* @param schemaType The type of documents which will be stored in this property.
* Documents of different types cannot be mixed into a single property.
*/
public Builder(@NonNull String propertyName, @NonNull String schemaType) {
mPropertyName = Objects.requireNonNull(propertyName);
mSchemaType = Objects.requireNonNull(schemaType);
}
/**
* The cardinality of the property (whether it is optional, required or repeated).
*
* <p>If this method is not called, the default cardinality is {@link
* PropertyConfig#CARDINALITY_OPTIONAL}.
*/
@SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
@NonNull
public DocumentPropertyConfig.Builder setCardinality(@Cardinality int cardinality) {
Preconditions.checkArgumentInRange(
cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality");
mCardinality = cardinality;
return this;
}
/**
* Configures whether fields in the nested document should be indexed according to that
* document's schema.
*
* <p>If false, the nested document's properties are not indexed regardless of its own
* schema.
*/
@NonNull
public DocumentPropertyConfig.Builder setShouldIndexNestedProperties(
boolean indexNestedProperties) {
mShouldIndexNestedProperties = indexNestedProperties;
return this;
}
/** Constructs a new {@link PropertyConfig} from the contents of this builder. */
@NonNull
public DocumentPropertyConfig build() {
Bundle bundle = new Bundle();
bundle.putString(NAME_FIELD, mPropertyName);
bundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_DOCUMENT);
bundle.putInt(CARDINALITY_FIELD, mCardinality);
bundle.putBoolean(INDEX_NESTED_PROPERTIES_FIELD, mShouldIndexNestedProperties);
bundle.putString(SCHEMA_TYPE_FIELD, mSchemaType);
return new DocumentPropertyConfig(bundle);
}
}
/**
* Appends a debug string for the {@link DocumentPropertyConfig} instance to the given
* string builder.
*
* <p>This appends fields specific to a {@link DocumentPropertyConfig} instance.
*
* @param builder the builder to append to.
*/
void appendDocumentPropertyConfigFields(@NonNull IndentingStringBuilder builder) {
builder.append("shouldIndexNestedProperties: ")
.append(shouldIndexNestedProperties())
.append(",\n");
builder.append("schemaType: \"").append(getSchemaType()).append("\",\n");
}
}
}

View File

@@ -1,181 +0,0 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch;
import android.annotation.NonNull;
import android.util.ArrayMap;
import android.util.ArraySet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* Encapsulates a request to retrieve documents by namespace and IDs from the {@link
* AppSearchSession} database.
*
* @see AppSearchSession#getByDocumentId
*/
public final class GetByDocumentIdRequest {
/**
* Schema type to be used in {@link GetByDocumentIdRequest.Builder#addProjection} to apply
* property paths to all results, excepting any types that have had their own, specific property
* paths set.
*/
public static final String PROJECTION_SCHEMA_TYPE_WILDCARD = "*";
private final String mNamespace;
private final Set<String> mIds;
private final Map<String, List<String>> mTypePropertyPathsMap;
GetByDocumentIdRequest(
@NonNull String namespace,
@NonNull Set<String> ids,
@NonNull Map<String, List<String>> typePropertyPathsMap) {
mNamespace = Objects.requireNonNull(namespace);
mIds = Objects.requireNonNull(ids);
mTypePropertyPathsMap = Objects.requireNonNull(typePropertyPathsMap);
}
/** Returns the namespace attached to the request. */
@NonNull
public String getNamespace() {
return mNamespace;
}
/** Returns the set of document IDs attached to the request. */
@NonNull
public Set<String> getIds() {
return Collections.unmodifiableSet(mIds);
}
/**
* Returns a map from schema type to property paths to be used for projection.
*
* <p>If the map is empty, then all properties will be retrieved for all results.
*
* <p>Calling this function repeatedly is inefficient. Prefer to retain the Map returned by this
* function, rather than calling it multiple times.
*/
@NonNull
public Map<String, List<String>> getProjections() {
Map<String, List<String>> copy = new ArrayMap<>();
for (Map.Entry<String, List<String>> entry : mTypePropertyPathsMap.entrySet()) {
copy.put(entry.getKey(), new ArrayList<>(entry.getValue()));
}
return copy;
}
/**
* Returns a map from schema type to property paths to be used for projection.
*
* <p>If the map is empty, then all properties will be retrieved for all results.
*
* <p>A more efficient version of {@link #getProjections}, but it returns a modifiable map. This
* is not meant to be unhidden and should only be used by internal classes.
*
* @hide
*/
@NonNull
public Map<String, List<String>> getProjectionsInternal() {
return mTypePropertyPathsMap;
}
/** Builder for {@link GetByDocumentIdRequest} objects. */
public static final class Builder {
private final String mNamespace;
private ArraySet<String> mIds = new ArraySet<>();
private ArrayMap<String, List<String>> mProjectionTypePropertyPaths = new ArrayMap<>();
private boolean mBuilt = false;
/** Creates a {@link GetByDocumentIdRequest.Builder} instance. */
public Builder(@NonNull String namespace) {
mNamespace = Objects.requireNonNull(namespace);
}
/** Adds one or more document IDs to the request. */
@NonNull
public Builder addIds(@NonNull String... ids) {
Objects.requireNonNull(ids);
resetIfBuilt();
return addIds(Arrays.asList(ids));
}
/** Adds a collection of IDs to the request. */
@NonNull
public Builder addIds(@NonNull Collection<String> ids) {
Objects.requireNonNull(ids);
resetIfBuilt();
mIds.addAll(ids);
return this;
}
/**
* Adds property paths for the specified type to be used for projection. If property paths
* are added for a type, then only the properties referred to will be retrieved for results
* of that type. If a property path that is specified isn't present in a result, it will be
* ignored for that result. Property paths cannot be null.
*
* <p>If no property paths are added for a particular type, then all properties of results
* of that type will be retrieved.
*
* <p>If property path is added for the {@link
* GetByDocumentIdRequest#PROJECTION_SCHEMA_TYPE_WILDCARD}, then those property paths will
* apply to all results, excepting any types that have their own, specific property paths
* set.
*
* @see SearchSpec.Builder#addProjection
*/
@NonNull
public Builder addProjection(
@NonNull String schemaType, @NonNull Collection<String> propertyPaths) {
Objects.requireNonNull(schemaType);
Objects.requireNonNull(propertyPaths);
resetIfBuilt();
List<String> propertyPathsList = new ArrayList<>(propertyPaths.size());
for (String propertyPath : propertyPaths) {
Objects.requireNonNull(propertyPath);
propertyPathsList.add(propertyPath);
}
mProjectionTypePropertyPaths.put(schemaType, propertyPathsList);
return this;
}
/** Builds a new {@link GetByDocumentIdRequest}. */
@NonNull
public GetByDocumentIdRequest build() {
mBuilt = true;
return new GetByDocumentIdRequest(mNamespace, mIds, mProjectionTypePropertyPaths);
}
private void resetIfBuilt() {
if (mBuilt) {
mIds = new ArraySet<>(mIds);
// No need to clone each propertyPathsList inside mProjectionTypePropertyPaths since
// the builder only replaces it, never adds to it. So even if the builder is used
// again, the previous one will remain with the object.
mProjectionTypePropertyPaths = new ArrayMap<>(mProjectionTypePropertyPaths);
mBuilt = false;
}
}
}
}

View File

@@ -1,118 +0,0 @@
/*
* Copyright 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 android.app.appsearch;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.os.Bundle;
import android.util.ArraySet;
import java.util.ArrayList;
import java.util.Objects;
import java.util.Set;
/** The response class of {@link AppSearchSession#getSchema} */
public final class GetSchemaResponse {
private static final String VERSION_FIELD = "version";
private static final String SCHEMAS_FIELD = "schemas";
private final Bundle mBundle;
GetSchemaResponse(@NonNull Bundle bundle) {
mBundle = Objects.requireNonNull(bundle);
}
/**
* Returns the {@link Bundle} populated by this builder.
*
* @hide
*/
@NonNull
public Bundle getBundle() {
return mBundle;
}
/**
* Returns the overall database schema version.
*
* <p>If the database is empty, 0 will be returned.
*/
@IntRange(from = 0)
public int getVersion() {
return mBundle.getInt(VERSION_FIELD);
}
/**
* Return the schemas most recently successfully provided to {@link AppSearchSession#setSchema}.
*
* <p>It is inefficient to call this method repeatedly.
*/
@NonNull
public Set<AppSearchSchema> getSchemas() {
ArrayList<Bundle> schemaBundles = mBundle.getParcelableArrayList(SCHEMAS_FIELD);
Set<AppSearchSchema> schemas = new ArraySet<>(schemaBundles.size());
for (int i = 0; i < schemaBundles.size(); i++) {
schemas.add(new AppSearchSchema(schemaBundles.get(i)));
}
return schemas;
}
/** Builder for {@link GetSchemaResponse} objects. */
public static final class Builder {
private int mVersion = 0;
private ArrayList<Bundle> mSchemaBundles = new ArrayList<>();
private boolean mBuilt = false;
/**
* Sets the database overall schema version.
*
* <p>Default version is 0
*/
@NonNull
public Builder setVersion(@IntRange(from = 0) int version) {
resetIfBuilt();
mVersion = version;
return this;
}
/** Adds one {@link AppSearchSchema} to the schema list. */
@NonNull
public Builder addSchema(@NonNull AppSearchSchema schema) {
Objects.requireNonNull(schema);
resetIfBuilt();
mSchemaBundles.add(schema.getBundle());
return this;
}
/** Builds a {@link GetSchemaResponse} object. */
@NonNull
public GetSchemaResponse build() {
Bundle bundle = new Bundle();
bundle.putInt(VERSION_FIELD, mVersion);
bundle.putParcelableArrayList(SCHEMAS_FIELD, mSchemaBundles);
mBuilt = true;
return new GetSchemaResponse(bundle);
}
private void resetIfBuilt() {
if (mBuilt) {
mSchemaBundles = new ArrayList<>(mSchemaBundles);
mBuilt = false;
}
}
}
}

View File

@@ -1,91 +0,0 @@
/*
* Copyright 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 android.app.appsearch;
import android.annotation.NonNull;
import android.annotation.WorkerThread;
/**
* A migrator class to translate {@link GenericDocument} from different version of {@link
* AppSearchSchema}
*
* <p>Make non-backwards-compatible changes will delete all stored documents in old schema. You can
* save your documents by setting {@link Migrator} via the {@link
* SetSchemaRequest.Builder#setMigrator} for each type and target version you want to save.
*
* <p>{@link #onDowngrade} or {@link #onUpgrade} will be triggered if the version number of the
* schema stored in AppSearch is different with the version in the request.
*
* <p>If any error or Exception occurred in the {@link #onDowngrade} or {@link #onUpgrade}, all the
* setSchema request will be rejected unless the schema changes are backwards-compatible, and stored
* documents won't have any observable changes.
*/
public abstract class Migrator {
/**
* Returns {@code true} if this migrator's source type needs to be migrated to update from
* currentVersion to finalVersion.
*
* <p>Migration won't be triggered if currentVersion is equal to finalVersion even if {@link
* #shouldMigrate} return true;
*/
public abstract boolean shouldMigrate(int currentVersion, int finalVersion);
/**
* Migrates {@link GenericDocument} to a newer version of {@link AppSearchSchema}.
*
* <p>This method will be invoked only if the {@link SetSchemaRequest} is setting a higher
* version number than the current {@link AppSearchSchema} saved in AppSearch.
*
* <p>If this {@link Migrator} is provided to cover a compatible schema change via {@link
* AppSearchSession#setSchema}, documents under the old version won't be removed unless you use
* the same document ID.
*
* <p>This method will be invoked on the background worker thread provided via {@link
* AppSearchSession#setSchema}.
*
* @param currentVersion The current version of the document's schema.
* @param finalVersion The final version that documents need to be migrated to.
* @param document The {@link GenericDocument} need to be translated to new version.
* @return A {@link GenericDocument} in new version.
*/
@WorkerThread
@NonNull
public abstract GenericDocument onUpgrade(
int currentVersion, int finalVersion, @NonNull GenericDocument document);
/**
* Migrates {@link GenericDocument} to an older version of {@link AppSearchSchema}.
*
* <p>This method will be invoked only if the {@link SetSchemaRequest} is setting a lower
* version number than the current {@link AppSearchSchema} saved in AppSearch.
*
* <p>If this {@link Migrator} is provided to cover a compatible schema change via {@link
* AppSearchSession#setSchema}, documents under the old version won't be removed unless you use
* the same document ID.
*
* <p>This method will be invoked on the background worker thread.
*
* @param currentVersion The current version of the document's schema.
* @param finalVersion The final version that documents need to be migrated to.
* @param document The {@link GenericDocument} need to be translated to new version.
* @return A {@link GenericDocument} in new version.
*/
@WorkerThread
@NonNull
public abstract GenericDocument onDowngrade(
int currentVersion, int finalVersion, @NonNull GenericDocument document);
}

View File

@@ -1,81 +0,0 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch;
import android.annotation.NonNull;
import android.app.appsearch.util.BundleUtil;
import android.os.Bundle;
import java.util.Objects;
/** This class represents a uniquely identifiable package. */
public class PackageIdentifier {
private static final String PACKAGE_NAME_FIELD = "packageName";
private static final String SHA256_CERTIFICATE_FIELD = "sha256Certificate";
private final Bundle mBundle;
/**
* Creates a unique identifier for a package.
*
* @param packageName Name of the package.
* @param sha256Certificate SHA256 certificate digest of the package.
*/
public PackageIdentifier(@NonNull String packageName, @NonNull byte[] sha256Certificate) {
mBundle = new Bundle();
mBundle.putString(PACKAGE_NAME_FIELD, packageName);
mBundle.putByteArray(SHA256_CERTIFICATE_FIELD, sha256Certificate);
}
/** @hide */
public PackageIdentifier(@NonNull Bundle bundle) {
mBundle = Objects.requireNonNull(bundle);
}
/** @hide */
@NonNull
public Bundle getBundle() {
return mBundle;
}
@NonNull
public String getPackageName() {
return Objects.requireNonNull(mBundle.getString(PACKAGE_NAME_FIELD));
}
@NonNull
public byte[] getSha256Certificate() {
return Objects.requireNonNull(mBundle.getByteArray(SHA256_CERTIFICATE_FIELD));
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || !(obj instanceof PackageIdentifier)) {
return false;
}
final PackageIdentifier other = (PackageIdentifier) obj;
return BundleUtil.deepEquals(mBundle, other.mBundle);
}
@Override
public int hashCode() {
return BundleUtil.deepHashCode(mBundle);
}
}

View File

@@ -1,84 +0,0 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch;
import android.annotation.NonNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
/**
* Encapsulates a request to index documents into an {@link AppSearchSession} database.
*
* @see AppSearchSession#put
*/
public final class PutDocumentsRequest {
private final List<GenericDocument> mDocuments;
PutDocumentsRequest(List<GenericDocument> documents) {
mDocuments = documents;
}
/** Returns a list of {@link GenericDocument} objects that are part of this request. */
@NonNull
public List<GenericDocument> getGenericDocuments() {
return Collections.unmodifiableList(mDocuments);
}
/** Builder for {@link PutDocumentsRequest} objects. */
public static final class Builder {
private ArrayList<GenericDocument> mDocuments = new ArrayList<>();
private boolean mBuilt = false;
/** Adds one or more {@link GenericDocument} objects to the request. */
@NonNull
public Builder addGenericDocuments(@NonNull GenericDocument... documents) {
Objects.requireNonNull(documents);
resetIfBuilt();
return addGenericDocuments(Arrays.asList(documents));
}
/** Adds a collection of {@link GenericDocument} objects to the request. */
@NonNull
public Builder addGenericDocuments(
@NonNull Collection<? extends GenericDocument> documents) {
Objects.requireNonNull(documents);
resetIfBuilt();
mDocuments.addAll(documents);
return this;
}
/** Creates a new {@link PutDocumentsRequest} object. */
@NonNull
public PutDocumentsRequest build() {
mBuilt = true;
return new PutDocumentsRequest(mDocuments);
}
private void resetIfBuilt() {
if (mBuilt) {
mDocuments = new ArrayList<>(mDocuments);
mBuilt = false;
}
}
}
}

View File

@@ -1,97 +0,0 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch;
import android.annotation.NonNull;
import android.util.ArraySet;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;
/**
* Encapsulates a request to remove documents by namespace and IDs from the {@link AppSearchSession}
* database.
*
* @see AppSearchSession#remove
*/
public final class RemoveByDocumentIdRequest {
private final String mNamespace;
private final Set<String> mIds;
RemoveByDocumentIdRequest(String namespace, Set<String> ids) {
mNamespace = namespace;
mIds = ids;
}
/** Returns the namespace to remove documents from. */
@NonNull
public String getNamespace() {
return mNamespace;
}
/** Returns the set of document IDs attached to the request. */
@NonNull
public Set<String> getIds() {
return Collections.unmodifiableSet(mIds);
}
/** Builder for {@link RemoveByDocumentIdRequest} objects. */
public static final class Builder {
private final String mNamespace;
private ArraySet<String> mIds = new ArraySet<>();
private boolean mBuilt = false;
/** Creates a {@link RemoveByDocumentIdRequest.Builder} instance. */
public Builder(@NonNull String namespace) {
mNamespace = Objects.requireNonNull(namespace);
}
/** Adds one or more document IDs to the request. */
@NonNull
public Builder addIds(@NonNull String... ids) {
Objects.requireNonNull(ids);
resetIfBuilt();
return addIds(Arrays.asList(ids));
}
/** Adds a collection of IDs to the request. */
@NonNull
public Builder addIds(@NonNull Collection<String> ids) {
Objects.requireNonNull(ids);
resetIfBuilt();
mIds.addAll(ids);
return this;
}
/** Builds a new {@link RemoveByDocumentIdRequest}. */
@NonNull
public RemoveByDocumentIdRequest build() {
mBuilt = true;
return new RemoveByDocumentIdRequest(mNamespace, mIds);
}
private void resetIfBuilt() {
if (mBuilt) {
mIds = new ArraySet<>(mIds);
mBuilt = false;
}
}
}
}

View File

@@ -1,145 +0,0 @@
/*
* Copyright 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 android.app.appsearch;
import android.annotation.CurrentTimeMillisLong;
import android.annotation.NonNull;
import java.util.Objects;
/**
* A request to report usage of a document owned by another app from a system UI surface.
*
* <p>Usage reported in this way is measured separately from usage reported via {@link
* AppSearchSession#reportUsage}.
*
* <p>See {@link GlobalSearchSession#reportSystemUsage} for a detailed description of usage
* reporting.
*/
public final class ReportSystemUsageRequest {
private final String mPackageName;
private final String mDatabase;
private final String mNamespace;
private final String mDocumentId;
private final long mUsageTimestampMillis;
ReportSystemUsageRequest(
@NonNull String packageName,
@NonNull String database,
@NonNull String namespace,
@NonNull String documentId,
long usageTimestampMillis) {
mPackageName = Objects.requireNonNull(packageName);
mDatabase = Objects.requireNonNull(database);
mNamespace = Objects.requireNonNull(namespace);
mDocumentId = Objects.requireNonNull(documentId);
mUsageTimestampMillis = usageTimestampMillis;
}
/** Returns the package name of the app which owns the document that was used. */
@NonNull
public String getPackageName() {
return mPackageName;
}
/** Returns the database in which the document that was used resides. */
@NonNull
public String getDatabaseName() {
return mDatabase;
}
/** Returns the namespace of the document that was used. */
@NonNull
public String getNamespace() {
return mNamespace;
}
/** Returns the ID of document that was used. */
@NonNull
public String getDocumentId() {
return mDocumentId;
}
/**
* Returns the timestamp in milliseconds of the usage report (the time at which the document was
* used).
*
* <p>The value is in the {@link System#currentTimeMillis} time base.
*/
@CurrentTimeMillisLong
public long getUsageTimestampMillis() {
return mUsageTimestampMillis;
}
/** Builder for {@link ReportSystemUsageRequest} objects. */
public static final class Builder {
private final String mPackageName;
private final String mDatabase;
private final String mNamespace;
private final String mDocumentId;
private Long mUsageTimestampMillis;
/**
* Creates a {@link ReportSystemUsageRequest.Builder} instance.
*
* @param packageName The package name of the app which owns the document that was used
* (e.g. from {@link SearchResult#getPackageName}).
* @param databaseName The database in which the document that was used resides (e.g. from
* {@link SearchResult#getDatabaseName}).
* @param namespace The namespace of the document that was used (e.g. from {@link
* GenericDocument#getNamespace}.
* @param documentId The ID of document that was used (e.g. from {@link
* GenericDocument#getId}.
*/
public Builder(
@NonNull String packageName,
@NonNull String databaseName,
@NonNull String namespace,
@NonNull String documentId) {
mPackageName = Objects.requireNonNull(packageName);
mDatabase = Objects.requireNonNull(databaseName);
mNamespace = Objects.requireNonNull(namespace);
mDocumentId = Objects.requireNonNull(documentId);
}
/**
* Sets the timestamp in milliseconds of the usage report (the time at which the document
* was used).
*
* <p>The value is in the {@link System#currentTimeMillis} time base.
*
* <p>If unset, this defaults to the current timestamp at the time that the {@link
* ReportSystemUsageRequest} is constructed.
*/
@NonNull
public ReportSystemUsageRequest.Builder setUsageTimestampMillis(
@CurrentTimeMillisLong long usageTimestampMillis) {
mUsageTimestampMillis = usageTimestampMillis;
return this;
}
/** Builds a new {@link ReportSystemUsageRequest}. */
@NonNull
public ReportSystemUsageRequest build() {
if (mUsageTimestampMillis == null) {
mUsageTimestampMillis = System.currentTimeMillis();
}
return new ReportSystemUsageRequest(
mPackageName, mDatabase, mNamespace, mDocumentId, mUsageTimestampMillis);
}
}
}

View File

@@ -1,110 +0,0 @@
/*
* Copyright 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 android.app.appsearch;
import android.annotation.CurrentTimeMillisLong;
import android.annotation.NonNull;
import java.util.Objects;
/**
* A request to report usage of a document.
*
* <p>See {@link AppSearchSession#reportUsage} for a detailed description of usage reporting.
*
* @see AppSearchSession#reportUsage
*/
public final class ReportUsageRequest {
private final String mNamespace;
private final String mDocumentId;
private final long mUsageTimestampMillis;
ReportUsageRequest(
@NonNull String namespace, @NonNull String documentId, long usageTimestampMillis) {
mNamespace = Objects.requireNonNull(namespace);
mDocumentId = Objects.requireNonNull(documentId);
mUsageTimestampMillis = usageTimestampMillis;
}
/** Returns the namespace of the document that was used. */
@NonNull
public String getNamespace() {
return mNamespace;
}
/** Returns the ID of document that was used. */
@NonNull
public String getDocumentId() {
return mDocumentId;
}
/**
* Returns the timestamp in milliseconds of the usage report (the time at which the document was
* used).
*
* <p>The value is in the {@link System#currentTimeMillis} time base.
*/
@CurrentTimeMillisLong
public long getUsageTimestampMillis() {
return mUsageTimestampMillis;
}
/** Builder for {@link ReportUsageRequest} objects. */
public static final class Builder {
private final String mNamespace;
private final String mDocumentId;
private Long mUsageTimestampMillis;
/**
* Creates a new {@link ReportUsageRequest.Builder} instance.
*
* @param namespace The namespace of the document that was used (e.g. from {@link
* GenericDocument#getNamespace}.
* @param documentId The ID of document that was used (e.g. from {@link
* GenericDocument#getId}.
*/
public Builder(@NonNull String namespace, @NonNull String documentId) {
mNamespace = Objects.requireNonNull(namespace);
mDocumentId = Objects.requireNonNull(documentId);
}
/**
* Sets the timestamp in milliseconds of the usage report (the time at which the document
* was used).
*
* <p>The value is in the {@link System#currentTimeMillis} time base.
*
* <p>If unset, this defaults to the current timestamp at the time that the {@link
* ReportUsageRequest} is constructed.
*/
@NonNull
public ReportUsageRequest.Builder setUsageTimestampMillis(
@CurrentTimeMillisLong long usageTimestampMillis) {
mUsageTimestampMillis = usageTimestampMillis;
return this;
}
/** Builds a new {@link ReportUsageRequest}. */
@NonNull
public ReportUsageRequest build() {
if (mUsageTimestampMillis == null) {
mUsageTimestampMillis = System.currentTimeMillis();
}
return new ReportUsageRequest(mNamespace, mDocumentId, mUsageTimestampMillis);
}
}
}

View File

@@ -1,549 +0,0 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* This class represents one of the results obtained from an AppSearch query.
*
* <p>This allows clients to obtain:
*
* <ul>
* <li>The document which matched, using {@link #getGenericDocument}
* <li>Information about which properties in the document matched, and "snippet" information
* containing textual summaries of the document's matches, using {@link #getMatchInfos}
* </ul>
*
* <p>"Snippet" refers to a substring of text from the content of document that is returned as a
* part of search result.
*
* @see SearchResults
*/
public final class SearchResult {
static final String DOCUMENT_FIELD = "document";
static final String MATCH_INFOS_FIELD = "matchInfos";
static final String PACKAGE_NAME_FIELD = "packageName";
static final String DATABASE_NAME_FIELD = "databaseName";
static final String RANKING_SIGNAL_FIELD = "rankingSignal";
@NonNull private final Bundle mBundle;
/** Cache of the inflated document. Comes from inflating mDocumentBundle at first use. */
@Nullable private GenericDocument mDocument;
/** Cache of the inflated matches. Comes from inflating mMatchBundles at first use. */
@Nullable private List<MatchInfo> mMatchInfos;
/** @hide */
public SearchResult(@NonNull Bundle bundle) {
mBundle = Objects.requireNonNull(bundle);
}
/** @hide */
@NonNull
public Bundle getBundle() {
return mBundle;
}
/**
* Contains the matching {@link GenericDocument}.
*
* @return Document object which matched the query.
*/
@NonNull
public GenericDocument getGenericDocument() {
if (mDocument == null) {
mDocument =
new GenericDocument(Objects.requireNonNull(mBundle.getBundle(DOCUMENT_FIELD)));
}
return mDocument;
}
/**
* Returns a list of {@link MatchInfo}s providing information about how the document in {@link
* #getGenericDocument} matched the query.
*
* @return List of matches based on {@link SearchSpec}. If snippeting is disabled using {@link
* SearchSpec.Builder#setSnippetCount} or {@link
* SearchSpec.Builder#setSnippetCountPerProperty}, for all results after that value, this
* method returns an empty list.
*/
@NonNull
public List<MatchInfo> getMatchInfos() {
if (mMatchInfos == null) {
List<Bundle> matchBundles =
Objects.requireNonNull(mBundle.getParcelableArrayList(MATCH_INFOS_FIELD));
mMatchInfos = new ArrayList<>(matchBundles.size());
for (int i = 0; i < matchBundles.size(); i++) {
MatchInfo matchInfo = new MatchInfo(matchBundles.get(i), getGenericDocument());
mMatchInfos.add(matchInfo);
}
}
return mMatchInfos;
}
/**
* Contains the package name of the app that stored the {@link GenericDocument}.
*
* @return Package name that stored the document
*/
@NonNull
public String getPackageName() {
return Objects.requireNonNull(mBundle.getString(PACKAGE_NAME_FIELD));
}
/**
* Contains the database name that stored the {@link GenericDocument}.
*
* @return Name of the database within which the document is stored
*/
@NonNull
public String getDatabaseName() {
return Objects.requireNonNull(mBundle.getString(DATABASE_NAME_FIELD));
}
/**
* Returns the ranking signal of the {@link GenericDocument}, according to the ranking strategy
* set in {@link SearchSpec.Builder#setRankingStrategy(int)}.
*
* <p>The meaning of the ranking signal and its value is determined by the selected ranking
* strategy:
*
* <ul>
* <li>{@link SearchSpec#RANKING_STRATEGY_NONE} - this value will be 0
* <li>{@link SearchSpec#RANKING_STRATEGY_DOCUMENT_SCORE} - the value returned by calling
* {@link GenericDocument#getScore()} on the document returned by {@link
* #getGenericDocument()}
* <li>{@link SearchSpec#RANKING_STRATEGY_CREATION_TIMESTAMP} - the value returned by calling
* {@link GenericDocument#getCreationTimestampMillis()} on the document returned by {@link
* #getGenericDocument()}
* <li>{@link SearchSpec#RANKING_STRATEGY_RELEVANCE_SCORE} - an arbitrary double value where a
* higher value means more relevant
* <li>{@link SearchSpec#RANKING_STRATEGY_USAGE_COUNT} - the number of times usage has been
* reported for the document returned by {@link #getGenericDocument()}
* <li>{@link SearchSpec#RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP} - the timestamp of the
* most recent usage that has been reported for the document returned by {@link
* #getGenericDocument()}
* </ul>
*
* @return Ranking signal of the document
*/
public double getRankingSignal() {
return mBundle.getDouble(RANKING_SIGNAL_FIELD);
}
/** Builder for {@link SearchResult} objects. */
public static final class Builder {
private final String mPackageName;
private final String mDatabaseName;
private ArrayList<Bundle> mMatchInfoBundles = new ArrayList<>();
private GenericDocument mGenericDocument;
private double mRankingSignal;
private boolean mBuilt = false;
/**
* Constructs a new builder for {@link SearchResult} objects.
*
* @param packageName the package name the matched document belongs to
* @param databaseName the database name the matched document belongs to.
*/
public Builder(@NonNull String packageName, @NonNull String databaseName) {
mPackageName = Objects.requireNonNull(packageName);
mDatabaseName = Objects.requireNonNull(databaseName);
}
/** Sets the document which matched. */
@NonNull
public Builder setGenericDocument(@NonNull GenericDocument document) {
Objects.requireNonNull(document);
resetIfBuilt();
mGenericDocument = document;
return this;
}
/** Adds another match to this SearchResult. */
@NonNull
public Builder addMatchInfo(@NonNull MatchInfo matchInfo) {
Preconditions.checkState(
matchInfo.mDocument == null,
"This MatchInfo is already associated with a SearchResult and can't be "
+ "reassigned");
resetIfBuilt();
mMatchInfoBundles.add(matchInfo.mBundle);
return this;
}
/** Sets the ranking signal of the matched document in this SearchResult. */
@NonNull
public Builder setRankingSignal(double rankingSignal) {
resetIfBuilt();
mRankingSignal = rankingSignal;
return this;
}
/** Constructs a new {@link SearchResult}. */
@NonNull
public SearchResult build() {
Bundle bundle = new Bundle();
bundle.putString(PACKAGE_NAME_FIELD, mPackageName);
bundle.putString(DATABASE_NAME_FIELD, mDatabaseName);
bundle.putBundle(DOCUMENT_FIELD, mGenericDocument.getBundle());
bundle.putDouble(RANKING_SIGNAL_FIELD, mRankingSignal);
bundle.putParcelableArrayList(MATCH_INFOS_FIELD, mMatchInfoBundles);
mBuilt = true;
return new SearchResult(bundle);
}
private void resetIfBuilt() {
if (mBuilt) {
mMatchInfoBundles = new ArrayList<>(mMatchInfoBundles);
mBuilt = false;
}
}
}
/**
* This class represents a match objects for any Snippets that might be present in {@link
* SearchResults} from query. Using this class user can get the full text, exact matches and
* Snippets of document content for a given match.
*
* <p>Class Example 1: A document contains following text in property subject:
*
* <p>A commonly used fake word is foo. Another nonsense word that’s used a lot is bar.
*
* <p>If the queryExpression is "foo".
*
* <p>{@link MatchInfo#getPropertyPath()} returns "subject"
*
* <p>{@link MatchInfo#getFullText()} returns "A commonly used fake word is foo. Another
* nonsense word that’s used a lot is bar."
*
* <p>{@link MatchInfo#getExactMatchRange()} returns [29, 32]
*
* <p>{@link MatchInfo#getExactMatch()} returns "foo"
*
* <p>{@link MatchInfo#getSnippetRange()} returns [26, 33]
*
* <p>{@link MatchInfo#getSnippet()} returns "is foo."
*
* <p>
*
* <p>Class Example 2: A document contains a property name sender which contains 2 property
* names name and email, so we will have 2 property paths: {@code sender.name} and {@code
* sender.email}.
*
* <p>Let {@code sender.name = "Test Name Jr."} and {@code sender.email =
* "TestNameJr@gmail.com"}
*
* <p>If the queryExpression is "Test". We will have 2 matches.
*
* <p>Match-1
*
* <p>{@link MatchInfo#getPropertyPath()} returns "sender.name"
*
* <p>{@link MatchInfo#getFullText()} returns "Test Name Jr."
*
* <p>{@link MatchInfo#getExactMatchRange()} returns [0, 4]
*
* <p>{@link MatchInfo#getExactMatch()} returns "Test"
*
* <p>{@link MatchInfo#getSnippetRange()} returns [0, 9]
*
* <p>{@link MatchInfo#getSnippet()} returns "Test Name"
*
* <p>Match-2
*
* <p>{@link MatchInfo#getPropertyPath()} returns "sender.email"
*
* <p>{@link MatchInfo#getFullText()} returns "TestNameJr@gmail.com"
*
* <p>{@link MatchInfo#getExactMatchRange()} returns [0, 20]
*
* <p>{@link MatchInfo#getExactMatch()} returns "TestNameJr@gmail.com"
*
* <p>{@link MatchInfo#getSnippetRange()} returns [0, 20]
*
* <p>{@link MatchInfo#getSnippet()} returns "TestNameJr@gmail.com"
*/
public static final class MatchInfo {
/** The path of the matching snippet property. */
private static final String PROPERTY_PATH_FIELD = "propertyPath";
private static final String EXACT_MATCH_RANGE_LOWER_FIELD = "exactMatchRangeLower";
private static final String EXACT_MATCH_RANGE_UPPER_FIELD = "exactMatchRangeUpper";
private static final String SNIPPET_RANGE_LOWER_FIELD = "snippetRangeLower";
private static final String SNIPPET_RANGE_UPPER_FIELD = "snippetRangeUpper";
private final String mPropertyPath;
final Bundle mBundle;
/**
* Document which the match comes from.
*
* <p>If this is {@code null}, methods which require access to the document, like {@link
* #getExactMatch}, will throw {@link NullPointerException}.
*/
@Nullable final GenericDocument mDocument;
/** Full text of the matched property. Populated on first use. */
@Nullable private String mFullText;
/** Range of property that exactly matched the query. Populated on first use. */
@Nullable private MatchRange mExactMatchRange;
/** Range of some reasonable amount of context around the query. Populated on first use. */
@Nullable private MatchRange mWindowRange;
MatchInfo(@NonNull Bundle bundle, @Nullable GenericDocument document) {
mBundle = Objects.requireNonNull(bundle);
mDocument = document;
mPropertyPath = Objects.requireNonNull(bundle.getString(PROPERTY_PATH_FIELD));
}
/**
* Gets the property path corresponding to the given entry.
*
* <p>A property path is a '.' - delimited sequence of property names indicating which
* property in the document these snippets correspond to.
*
* <p>Example properties: 'body', 'sender.name', 'sender.emailaddress', etc. For class
* example 1 this returns "subject"
*/
@NonNull
public String getPropertyPath() {
return mPropertyPath;
}
/**
* Gets the full text corresponding to the given entry.
*
* <p>For class example this returns "A commonly used fake word is foo. Another nonsense
* word that's used a lot is bar."
*/
@NonNull
public String getFullText() {
if (mFullText == null) {
Preconditions.checkState(
mDocument != null,
"Document has not been populated; this MatchInfo cannot be used yet");
mFullText = getPropertyValues(mDocument, mPropertyPath);
}
return mFullText;
}
/**
* Gets the exact {@link MatchRange} corresponding to the given entry.
*
* <p>For class example 1 this returns [29, 32]
*/
@NonNull
public MatchRange getExactMatchRange() {
if (mExactMatchRange == null) {
mExactMatchRange =
new MatchRange(
mBundle.getInt(EXACT_MATCH_RANGE_LOWER_FIELD),
mBundle.getInt(EXACT_MATCH_RANGE_UPPER_FIELD));
}
return mExactMatchRange;
}
/**
* Gets the {@link MatchRange} corresponding to the given entry.
*
* <p>For class example 1 this returns "foo"
*/
@NonNull
public CharSequence getExactMatch() {
return getSubstring(getExactMatchRange());
}
/**
* Gets the snippet {@link MatchRange} corresponding to the given entry.
*
* <p>Only populated when set maxSnippetSize > 0 in {@link
* SearchSpec.Builder#setMaxSnippetSize}.
*
* <p>For class example 1 this returns [29, 41].
*/
@NonNull
public MatchRange getSnippetRange() {
if (mWindowRange == null) {
mWindowRange =
new MatchRange(
mBundle.getInt(SNIPPET_RANGE_LOWER_FIELD),
mBundle.getInt(SNIPPET_RANGE_UPPER_FIELD));
}
return mWindowRange;
}
/**
* Gets the snippet corresponding to the given entry.
*
* <p>Snippet - Provides a subset of the content to display. Only populated when requested
* maxSnippetSize > 0. The size of this content can be changed by {@link
* SearchSpec.Builder#setMaxSnippetSize}. Windowing is centered around the middle of the
* matched token with content on either side clipped to token boundaries.
*
* <p>For class example 1 this returns "foo. Another"
*/
@NonNull
public CharSequence getSnippet() {
return getSubstring(getSnippetRange());
}
private CharSequence getSubstring(MatchRange range) {
return getFullText().substring(range.getStart(), range.getEnd());
}
/** Extracts the matching string from the document. */
private static String getPropertyValues(GenericDocument document, String propertyName) {
// In IcingLib snippeting is available for only 3 data types i.e String, double and
// long, so we need to check which of these three are requested.
// TODO (tytytyww): support double[] and long[].
String result = document.getPropertyString(propertyName);
if (result == null) {
throw new IllegalStateException(
"No content found for requested property path: " + propertyName);
}
return result;
}
/** Builder for {@link MatchInfo} objects. */
public static final class Builder {
private final String mPropertyPath;
private MatchRange mExactMatchRange = new MatchRange(0, 0);
private MatchRange mSnippetRange = new MatchRange(0, 0);
/**
* Creates a new {@link MatchInfo.Builder} reporting a match with the given property
* path.
*
* <p>A property path is a dot-delimited sequence of property names indicating which
* property in the document these snippets correspond to.
*
* <p>Example properties: 'body', 'sender.name', 'sender.emailaddress', etc.
* For class example 1 this returns "subject".
*
* @param propertyPath A {@code dot-delimited sequence of property names indicating
* which property in the document these snippets correspond to.
*/
public Builder(@NonNull String propertyPath) {
mPropertyPath = Objects.requireNonNull(propertyPath);
}
/** Sets the exact {@link MatchRange} corresponding to the given entry. */
@NonNull
public Builder setExactMatchRange(@NonNull MatchRange matchRange) {
mExactMatchRange = Objects.requireNonNull(matchRange);
return this;
}
/** Sets the snippet {@link MatchRange} corresponding to the given entry. */
@NonNull
public Builder setSnippetRange(@NonNull MatchRange matchRange) {
mSnippetRange = Objects.requireNonNull(matchRange);
return this;
}
/** Constructs a new {@link MatchInfo}. */
@NonNull
public MatchInfo build() {
Bundle bundle = new Bundle();
bundle.putString(SearchResult.MatchInfo.PROPERTY_PATH_FIELD, mPropertyPath);
bundle.putInt(MatchInfo.EXACT_MATCH_RANGE_LOWER_FIELD, mExactMatchRange.getStart());
bundle.putInt(MatchInfo.EXACT_MATCH_RANGE_UPPER_FIELD, mExactMatchRange.getEnd());
bundle.putInt(MatchInfo.SNIPPET_RANGE_LOWER_FIELD, mSnippetRange.getStart());
bundle.putInt(MatchInfo.SNIPPET_RANGE_UPPER_FIELD, mSnippetRange.getEnd());
return new MatchInfo(bundle, /*document=*/ null);
}
}
}
/**
* Class providing the position range of matching information.
*
* <p>All ranges are finite, and the left side of the range is always {@code <=} the right side
* of the range.
*
* <p>Example: MatchRange(0, 100) represent a hundred ints from 0 to 99."
*/
public static final class MatchRange {
private final int mEnd;
private final int mStart;
/**
* Creates a new immutable range.
*
* <p>The endpoints are {@code [start, end)}; that is the range is bounded. {@code start}
* must be lesser or equal to {@code end}.
*
* @param start The start point (inclusive)
* @param end The end point (exclusive)
*/
public MatchRange(int start, int end) {
if (start > end) {
throw new IllegalArgumentException(
"Start point must be less than or equal to " + "end point");
}
mStart = start;
mEnd = end;
}
/** Gets the start point (inclusive). */
public int getStart() {
return mStart;
}
/** Gets the end point (exclusive). */
public int getEnd() {
return mEnd;
}
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof MatchRange)) {
return false;
}
MatchRange otherMatchRange = (MatchRange) other;
return this.getStart() == otherMatchRange.getStart()
&& this.getEnd() == otherMatchRange.getEnd();
}
@Override
@NonNull
public String toString() {
return "MatchRange { start: " + mStart + " , end: " + mEnd + "}";
}
@Override
public int hashCode() {
return Objects.hash(mStart, mEnd);
}
}
}

View File

@@ -1,74 +0,0 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
/**
* This class represents a page of {@link SearchResult}s
*
* @hide
*/
public class SearchResultPage {
public static final String RESULTS_FIELD = "results";
public static final String NEXT_PAGE_TOKEN_FIELD = "nextPageToken";
private final long mNextPageToken;
@Nullable private List<SearchResult> mResults;
@NonNull private final Bundle mBundle;
public SearchResultPage(@NonNull Bundle bundle) {
mBundle = Objects.requireNonNull(bundle);
mNextPageToken = mBundle.getLong(NEXT_PAGE_TOKEN_FIELD);
}
/** Returns the {@link Bundle} of this class. */
@NonNull
public Bundle getBundle() {
return mBundle;
}
/** Returns the Token to get next {@link SearchResultPage}. */
public long getNextPageToken() {
return mNextPageToken;
}
/** Returns all {@link android.app.appsearch.SearchResult}s of this page */
@NonNull
public List<SearchResult> getResults() {
if (mResults == null) {
ArrayList<Bundle> resultBundles = mBundle.getParcelableArrayList(RESULTS_FIELD);
if (resultBundles == null) {
mResults = Collections.emptyList();
} else {
mResults = new ArrayList<>(resultBundles.size());
for (int i = 0; i < resultBundles.size(); i++) {
mResults.add(new SearchResult(resultBundles.get(i)));
}
}
}
return mResults;
}
}

View File

@@ -1,676 +0,0 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.app.appsearch.util.BundleUtil;
import android.os.Bundle;
import android.util.ArrayMap;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* This class represents the specification logic for AppSearch. It can be used to set the type of
* search, like prefix or exact only or apply filters to search for a specific schema type only etc.
*/
// TODO(sidchhabra) : AddResultSpec fields for Snippets etc.
public final class SearchSpec {
/**
* Schema type to be used in {@link SearchSpec.Builder#addProjection} to apply property paths to
* all results, excepting any types that have had their own, specific property paths set.
*/
public static final String PROJECTION_SCHEMA_TYPE_WILDCARD = "*";
static final String TERM_MATCH_TYPE_FIELD = "termMatchType";
static final String SCHEMA_FIELD = "schema";
static final String NAMESPACE_FIELD = "namespace";
static final String PACKAGE_NAME_FIELD = "packageName";
static final String NUM_PER_PAGE_FIELD = "numPerPage";
static final String RANKING_STRATEGY_FIELD = "rankingStrategy";
static final String ORDER_FIELD = "order";
static final String SNIPPET_COUNT_FIELD = "snippetCount";
static final String SNIPPET_COUNT_PER_PROPERTY_FIELD = "snippetCountPerProperty";
static final String MAX_SNIPPET_FIELD = "maxSnippet";
static final String PROJECTION_TYPE_PROPERTY_PATHS_FIELD = "projectionTypeFieldMasks";
static final String RESULT_GROUPING_TYPE_FLAGS = "resultGroupingTypeFlags";
static final String RESULT_GROUPING_LIMIT = "resultGroupingLimit";
/** @hide */
public static final int DEFAULT_NUM_PER_PAGE = 10;
// TODO(b/170371356): In framework, we may want these limits to be flag controlled.
// If that happens, the @IntRange() directives in this class may have to change.
private static final int MAX_NUM_PER_PAGE = 10_000;
private static final int MAX_SNIPPET_COUNT = 10_000;
private static final int MAX_SNIPPET_PER_PROPERTY_COUNT = 10_000;
private static final int MAX_SNIPPET_SIZE_LIMIT = 10_000;
/**
* Term Match Type for the query.
*
* @hide
*/
// NOTE: The integer values of these constants must match the proto enum constants in
// {@link com.google.android.icing.proto.SearchSpecProto.termMatchType}
@IntDef(value = {TERM_MATCH_EXACT_ONLY, TERM_MATCH_PREFIX})
@Retention(RetentionPolicy.SOURCE)
public @interface TermMatch {}
/**
* Query terms will only match exact tokens in the index.
*
* <p>Ex. A query term "foo" will only match indexed token "foo", and not "foot" or "football".
*/
public static final int TERM_MATCH_EXACT_ONLY = 1;
/**
* Query terms will match indexed tokens when the query term is a prefix of the token.
*
* <p>Ex. A query term "foo" will match indexed tokens like "foo", "foot", and "football".
*/
public static final int TERM_MATCH_PREFIX = 2;
/**
* Ranking Strategy for query result.
*
* @hide
*/
// NOTE: The integer values of these constants must match the proto enum constants in
// {@link ScoringSpecProto.RankingStrategy.Code}
@IntDef(
value = {
RANKING_STRATEGY_NONE,
RANKING_STRATEGY_DOCUMENT_SCORE,
RANKING_STRATEGY_CREATION_TIMESTAMP,
RANKING_STRATEGY_RELEVANCE_SCORE,
RANKING_STRATEGY_USAGE_COUNT,
RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP,
RANKING_STRATEGY_SYSTEM_USAGE_COUNT,
RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP,
})
@Retention(RetentionPolicy.SOURCE)
public @interface RankingStrategy {}
/** No Ranking, results are returned in arbitrary order. */
public static final int RANKING_STRATEGY_NONE = 0;
/** Ranked by app-provided document scores. */
public static final int RANKING_STRATEGY_DOCUMENT_SCORE = 1;
/** Ranked by document creation timestamps. */
public static final int RANKING_STRATEGY_CREATION_TIMESTAMP = 2;
/** Ranked by document relevance score. */
public static final int RANKING_STRATEGY_RELEVANCE_SCORE = 3;
/** Ranked by number of usages, as reported by the app. */
public static final int RANKING_STRATEGY_USAGE_COUNT = 4;
/** Ranked by timestamp of last usage, as reported by the app. */
public static final int RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP = 5;
/** Ranked by number of usages from a system UI surface. */
public static final int RANKING_STRATEGY_SYSTEM_USAGE_COUNT = 6;
/** Ranked by timestamp of last usage from a system UI surface. */
public static final int RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP = 7;
/**
* Order for query result.
*
* @hide
*/
// NOTE: The integer values of these constants must match the proto enum constants in
// {@link ScoringSpecProto.Order.Code}
@IntDef(value = {ORDER_DESCENDING, ORDER_ASCENDING})
@Retention(RetentionPolicy.SOURCE)
public @interface Order {}
/** Search results will be returned in a descending order. */
public static final int ORDER_DESCENDING = 0;
/** Search results will be returned in an ascending order. */
public static final int ORDER_ASCENDING = 1;
/**
* Grouping type for result limits.
*
* @hide
*/
@IntDef(
flag = true,
value = {GROUPING_TYPE_PER_PACKAGE, GROUPING_TYPE_PER_NAMESPACE})
@Retention(RetentionPolicy.SOURCE)
public @interface GroupingType {}
/**
* Results should be grouped together by package for the purpose of enforcing a limit on the
* number of results returned per package.
*/
public static final int GROUPING_TYPE_PER_PACKAGE = 0b01;
/**
* Results should be grouped together by namespace for the purpose of enforcing a limit on the
* number of results returned per namespace.
*/
public static final int GROUPING_TYPE_PER_NAMESPACE = 0b10;
private final Bundle mBundle;
/** @hide */
public SearchSpec(@NonNull Bundle bundle) {
Objects.requireNonNull(bundle);
mBundle = bundle;
}
/**
* Returns the {@link Bundle} populated by this builder.
*
* @hide
*/
@NonNull
public Bundle getBundle() {
return mBundle;
}
/** Returns how the query terms should match terms in the index. */
public @TermMatch int getTermMatch() {
return mBundle.getInt(TERM_MATCH_TYPE_FIELD, -1);
}
/**
* Returns the list of schema types to search for.
*
* <p>If empty, the query will search over all schema types.
*/
@NonNull
public List<String> getFilterSchemas() {
List<String> schemas = mBundle.getStringArrayList(SCHEMA_FIELD);
if (schemas == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(schemas);
}
/**
* Returns the list of namespaces to search over.
*
* <p>If empty, the query will search over all namespaces.
*/
@NonNull
public List<String> getFilterNamespaces() {
List<String> namespaces = mBundle.getStringArrayList(NAMESPACE_FIELD);
if (namespaces == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(namespaces);
}
/**
* Returns the list of package name filters to search over.
*
* <p>If empty, the query will search over all packages that the caller has access to. If
* package names are specified which caller doesn't have access to, then those package names
* will be ignored.
*/
@NonNull
public List<String> getFilterPackageNames() {
List<String> packageNames = mBundle.getStringArrayList(PACKAGE_NAME_FIELD);
if (packageNames == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(packageNames);
}
/** Returns the number of results per page in the result set. */
public int getResultCountPerPage() {
return mBundle.getInt(NUM_PER_PAGE_FIELD, DEFAULT_NUM_PER_PAGE);
}
/** Returns the ranking strategy. */
public @RankingStrategy int getRankingStrategy() {
return mBundle.getInt(RANKING_STRATEGY_FIELD);
}
/** Returns the order of returned search results (descending or ascending). */
public @Order int getOrder() {
return mBundle.getInt(ORDER_FIELD);
}
/** Returns how many documents to generate snippets for. */
public int getSnippetCount() {
return mBundle.getInt(SNIPPET_COUNT_FIELD);
}
/**
* Returns how many matches for each property of a matching document to generate snippets for.
*/
public int getSnippetCountPerProperty() {
return mBundle.getInt(SNIPPET_COUNT_PER_PROPERTY_FIELD);
}
/** Returns the maximum size of a snippet in characters. */
public int getMaxSnippetSize() {
return mBundle.getInt(MAX_SNIPPET_FIELD);
}
/**
* Returns a map from schema type to property paths to be used for projection.
*
* <p>If the map is empty, then all properties will be retrieved for all results.
*
* <p>Calling this function repeatedly is inefficient. Prefer to retain the Map returned by this
* function, rather than calling it multiple times.
*/
@NonNull
public Map<String, List<String>> getProjections() {
Bundle typePropertyPathsBundle = mBundle.getBundle(PROJECTION_TYPE_PROPERTY_PATHS_FIELD);
Set<String> schemas = typePropertyPathsBundle.keySet();
Map<String, List<String>> typePropertyPathsMap = new ArrayMap<>(schemas.size());
for (String schema : schemas) {
typePropertyPathsMap.put(schema, typePropertyPathsBundle.getStringArrayList(schema));
}
return typePropertyPathsMap;
}
/**
* Get the type of grouping limit to apply, or 0 if {@link Builder#setResultGrouping} was not
* called.
*/
public @GroupingType int getResultGroupingTypeFlags() {
return mBundle.getInt(RESULT_GROUPING_TYPE_FLAGS);
}
/**
* Get the maximum number of results to return for each group.
*
* @return the maximum number of results to return for each group or Integer.MAX_VALUE if {@link
* Builder#setResultGrouping(int, int)} was not called.
*/
public int getResultGroupingLimit() {
return mBundle.getInt(RESULT_GROUPING_LIMIT, Integer.MAX_VALUE);
}
/** Builder for {@link SearchSpec objects}. */
public static final class Builder {
private ArrayList<String> mSchemas = new ArrayList<>();
private ArrayList<String> mNamespaces = new ArrayList<>();
private ArrayList<String> mPackageNames = new ArrayList<>();
private Bundle mProjectionTypePropertyMasks = new Bundle();
private int mResultCountPerPage = DEFAULT_NUM_PER_PAGE;
private @TermMatch int mTermMatchType = TERM_MATCH_PREFIX;
private int mSnippetCount = 0;
private int mSnippetCountPerProperty = MAX_SNIPPET_PER_PROPERTY_COUNT;
private int mMaxSnippetSize = 0;
private @RankingStrategy int mRankingStrategy = RANKING_STRATEGY_NONE;
private @Order int mOrder = ORDER_DESCENDING;
private @GroupingType int mGroupingTypeFlags = 0;
private int mGroupingLimit = 0;
private boolean mBuilt = false;
/**
* Indicates how the query terms should match {@code TermMatchCode} in the index.
*
* <p>If this method is not called, the default term match type is {@link
* SearchSpec#TERM_MATCH_PREFIX}.
*/
@NonNull
public Builder setTermMatch(@TermMatch int termMatchType) {
Preconditions.checkArgumentInRange(
termMatchType, TERM_MATCH_EXACT_ONLY, TERM_MATCH_PREFIX, "Term match type");
resetIfBuilt();
mTermMatchType = termMatchType;
return this;
}
/**
* Adds a Schema type filter to {@link SearchSpec} Entry. Only search for documents that
* have the specified schema types.
*
* <p>If unset, the query will search over all schema types.
*/
@NonNull
public Builder addFilterSchemas(@NonNull String... schemas) {
Objects.requireNonNull(schemas);
resetIfBuilt();
return addFilterSchemas(Arrays.asList(schemas));
}
/**
* Adds a Schema type filter to {@link SearchSpec} Entry. Only search for documents that
* have the specified schema types.
*
* <p>If unset, the query will search over all schema types.
*/
@NonNull
public Builder addFilterSchemas(@NonNull Collection<String> schemas) {
Objects.requireNonNull(schemas);
resetIfBuilt();
mSchemas.addAll(schemas);
return this;
}
/**
* Adds a namespace filter to {@link SearchSpec} Entry. Only search for documents that have
* the specified namespaces.
*
* <p>If unset, the query will search over all namespaces.
*/
@NonNull
public Builder addFilterNamespaces(@NonNull String... namespaces) {
Objects.requireNonNull(namespaces);
resetIfBuilt();
return addFilterNamespaces(Arrays.asList(namespaces));
}
/**
* Adds a namespace filter to {@link SearchSpec} Entry. Only search for documents that have
* the specified namespaces.
*
* <p>If unset, the query will search over all namespaces.
*/
@NonNull
public Builder addFilterNamespaces(@NonNull Collection<String> namespaces) {
Objects.requireNonNull(namespaces);
resetIfBuilt();
mNamespaces.addAll(namespaces);
return this;
}
/**
* Adds a package name filter to {@link SearchSpec} Entry. Only search for documents that
* were indexed from the specified packages.
*
* <p>If unset, the query will search over all packages that the caller has access to. If
* package names are specified which caller doesn't have access to, then those package names
* will be ignored.
*/
@NonNull
public Builder addFilterPackageNames(@NonNull String... packageNames) {
Objects.requireNonNull(packageNames);
resetIfBuilt();
return addFilterPackageNames(Arrays.asList(packageNames));
}
/**
* Adds a package name filter to {@link SearchSpec} Entry. Only search for documents that
* were indexed from the specified packages.
*
* <p>If unset, the query will search over all packages that the caller has access to. If
* package names are specified which caller doesn't have access to, then those package names
* will be ignored.
*/
@NonNull
public Builder addFilterPackageNames(@NonNull Collection<String> packageNames) {
Objects.requireNonNull(packageNames);
resetIfBuilt();
mPackageNames.addAll(packageNames);
return this;
}
/**
* Sets the number of results per page in the returned object.
*
* <p>The default number of results per page is 10.
*/
@NonNull
public SearchSpec.Builder setResultCountPerPage(
@IntRange(from = 0, to = MAX_NUM_PER_PAGE) int resultCountPerPage) {
Preconditions.checkArgumentInRange(
resultCountPerPage, 0, MAX_NUM_PER_PAGE, "resultCountPerPage");
resetIfBuilt();
mResultCountPerPage = resultCountPerPage;
return this;
}
/** Sets ranking strategy for AppSearch results. */
@NonNull
public Builder setRankingStrategy(@RankingStrategy int rankingStrategy) {
Preconditions.checkArgumentInRange(
rankingStrategy,
RANKING_STRATEGY_NONE,
RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP,
"Result ranking strategy");
resetIfBuilt();
mRankingStrategy = rankingStrategy;
return this;
}
/**
* Indicates the order of returned search results, the default is {@link #ORDER_DESCENDING},
* meaning that results with higher scores come first.
*
* <p>This order field will be ignored if RankingStrategy = {@code RANKING_STRATEGY_NONE}.
*/
@NonNull
public Builder setOrder(@Order int order) {
Preconditions.checkArgumentInRange(
order, ORDER_DESCENDING, ORDER_ASCENDING, "Result ranking order");
resetIfBuilt();
mOrder = order;
return this;
}
/**
* Only the first {@code snippetCount} documents based on the ranking strategy will have
* snippet information provided.
*
* <p>The list returned from {@link SearchResult#getMatchInfos} will contain at most this
* many entries.
*
* <p>If set to 0 (default), snippeting is disabled and the list returned from {@link
* SearchResult#getMatchInfos} will be empty.
*/
@NonNull
public SearchSpec.Builder setSnippetCount(
@IntRange(from = 0, to = MAX_SNIPPET_COUNT) int snippetCount) {
Preconditions.checkArgumentInRange(snippetCount, 0, MAX_SNIPPET_COUNT, "snippetCount");
resetIfBuilt();
mSnippetCount = snippetCount;
return this;
}
/**
* Sets {@code snippetCountPerProperty}. Only the first {@code snippetCountPerProperty}
* snippets for each property of each {@link GenericDocument} will contain snippet
* information.
*
* <p>If set to 0, snippeting is disabled and the list returned from {@link
* SearchResult#getMatchInfos} will be empty.
*
* <p>The default behavior is to snippet all matches a property contains, up to the maximum
* value of 10,000.
*/
@NonNull
public SearchSpec.Builder setSnippetCountPerProperty(
@IntRange(from = 0, to = MAX_SNIPPET_PER_PROPERTY_COUNT)
int snippetCountPerProperty) {
Preconditions.checkArgumentInRange(
snippetCountPerProperty,
0,
MAX_SNIPPET_PER_PROPERTY_COUNT,
"snippetCountPerProperty");
resetIfBuilt();
mSnippetCountPerProperty = snippetCountPerProperty;
return this;
}
/**
* Sets {@code maxSnippetSize}, the maximum snippet size. Snippet windows start at {@code
* maxSnippetSize/2} bytes before the middle of the matching token and end at {@code
* maxSnippetSize/2} bytes after the middle of the matching token. It respects token
* boundaries, therefore the returned window may be smaller than requested.
*
* <p>Setting {@code maxSnippetSize} to 0 will disable windowing and an empty string will be
* returned. If matches enabled is also set to false, then snippeting is disabled.
*
* <p>Ex. {@code maxSnippetSize} = 16. "foo bar baz bat rat" with a query of "baz" will
* return a window of "bar baz bat" which is only 11 bytes long.
*/
@NonNull
public SearchSpec.Builder setMaxSnippetSize(
@IntRange(from = 0, to = MAX_SNIPPET_SIZE_LIMIT) int maxSnippetSize) {
Preconditions.checkArgumentInRange(
maxSnippetSize, 0, MAX_SNIPPET_SIZE_LIMIT, "maxSnippetSize");
resetIfBuilt();
mMaxSnippetSize = maxSnippetSize;
return this;
}
/**
* Adds property paths for the specified type to be used for projection. If property paths
* are added for a type, then only the properties referred to will be retrieved for results
* of that type. If a property path that is specified isn't present in a result, it will be
* ignored for that result. Property paths cannot be null.
*
* <p>If no property paths are added for a particular type, then all properties of results
* of that type will be retrieved.
*
* <p>If property path is added for the {@link SearchSpec#PROJECTION_SCHEMA_TYPE_WILDCARD},
* then those property paths will apply to all results, excepting any types that have their
* own, specific property paths set.
*
* <p>Suppose the following document is in the index.
*
* <pre>{@code
* Email: Document {
* sender: Document {
* name: "Mr. Person"
* email: "mrperson123@google.com"
* }
* recipients: [
* Document {
* name: "John Doe"
* email: "johndoe123@google.com"
* }
* Document {
* name: "Jane Doe"
* email: "janedoe123@google.com"
* }
* ]
* subject: "IMPORTANT"
* body: "Limited time offer!"
* }
* }</pre>
*
* <p>Then, suppose that a query for "important" is issued with the following projection
* type property paths:
*
* <pre>{@code
* {schema: "Email", ["subject", "sender.name", "recipients.name"]}
* }</pre>
*
* <p>The above document will be returned as:
*
* <pre>{@code
* Email: Document {
* sender: Document {
* name: "Mr. Body"
* }
* recipients: [
* Document {
* name: "John Doe"
* }
* Document {
* name: "Jane Doe"
* }
* ]
* subject: "IMPORTANT"
* }
* }</pre>
*/
@NonNull
public SearchSpec.Builder addProjection(
@NonNull String schema, @NonNull Collection<String> propertyPaths) {
Objects.requireNonNull(schema);
Objects.requireNonNull(propertyPaths);
resetIfBuilt();
ArrayList<String> propertyPathsArrayList = new ArrayList<>(propertyPaths.size());
for (String propertyPath : propertyPaths) {
Objects.requireNonNull(propertyPath);
propertyPathsArrayList.add(propertyPath);
}
mProjectionTypePropertyMasks.putStringArrayList(schema, propertyPathsArrayList);
return this;
}
/**
* Set the maximum number of results to return for each group, where groups are defined by
* grouping type.
*
* <p>Calling this method will override any previous calls. So calling
* setResultGrouping(GROUPING_TYPE_PER_PACKAGE, 7) and then calling
* setResultGrouping(GROUPING_TYPE_PER_PACKAGE, 2) will result in only the latter, a limit
* of two results per package, being applied. Or calling setResultGrouping
* (GROUPING_TYPE_PER_PACKAGE, 1) and then calling setResultGrouping
* (GROUPING_TYPE_PER_PACKAGE | GROUPING_PER_NAMESPACE, 5) will result in five results per
* package per namespace.
*
* @param groupingTypeFlags One or more combination of grouping types.
* @param limit Number of results to return per {@code groupingTypeFlags}.
* @throws IllegalArgumentException if groupingTypeFlags is zero.
*/
// Individual parameters available from getResultGroupingTypeFlags and
// getResultGroupingLimit
@SuppressLint("MissingGetterMatchingBuilder")
@NonNull
public Builder setResultGrouping(@GroupingType int groupingTypeFlags, int limit) {
Preconditions.checkState(
groupingTypeFlags != 0, "Result grouping type cannot be zero.");
resetIfBuilt();
mGroupingTypeFlags = groupingTypeFlags;
mGroupingLimit = limit;
return this;
}
/** Constructs a new {@link SearchSpec} from the contents of this builder. */
@NonNull
public SearchSpec build() {
Bundle bundle = new Bundle();
bundle.putStringArrayList(SCHEMA_FIELD, mSchemas);
bundle.putStringArrayList(NAMESPACE_FIELD, mNamespaces);
bundle.putStringArrayList(PACKAGE_NAME_FIELD, mPackageNames);
bundle.putBundle(PROJECTION_TYPE_PROPERTY_PATHS_FIELD, mProjectionTypePropertyMasks);
bundle.putInt(NUM_PER_PAGE_FIELD, mResultCountPerPage);
bundle.putInt(TERM_MATCH_TYPE_FIELD, mTermMatchType);
bundle.putInt(SNIPPET_COUNT_FIELD, mSnippetCount);
bundle.putInt(SNIPPET_COUNT_PER_PROPERTY_FIELD, mSnippetCountPerProperty);
bundle.putInt(MAX_SNIPPET_FIELD, mMaxSnippetSize);
bundle.putInt(RANKING_STRATEGY_FIELD, mRankingStrategy);
bundle.putInt(ORDER_FIELD, mOrder);
bundle.putInt(RESULT_GROUPING_TYPE_FLAGS, mGroupingTypeFlags);
bundle.putInt(RESULT_GROUPING_LIMIT, mGroupingLimit);
mBuilt = true;
return new SearchSpec(bundle);
}
private void resetIfBuilt() {
if (mBuilt) {
mSchemas = new ArrayList<>(mSchemas);
mNamespaces = new ArrayList<>(mNamespaces);
mPackageNames = new ArrayList<>(mPackageNames);
mProjectionTypePropertyMasks = BundleUtil.deepCopy(mProjectionTypePropertyMasks);
mBuilt = false;
}
}
}
}

View File

@@ -1,461 +0,0 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.util.ArrayMap;
import android.util.ArraySet;
import com.android.internal.util.Preconditions;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* Encapsulates a request to update the schema of an {@link AppSearchSession} database.
*
* <p>The schema is composed of a collection of {@link AppSearchSchema} objects, each of which
* defines a unique type of data.
*
* <p>The first call to SetSchemaRequest will set the provided schema and store it within the {@link
* AppSearchSession} database.
*
* <p>Subsequent calls will compare the provided schema to the previously saved schema, to determine
* how to treat existing documents.
*
* <p>The following types of schema modifications are always safe and are made without deleting any
* existing documents:
*
* <ul>
* <li>Addition of new {@link AppSearchSchema} types
* <li>Addition of new properties to an existing {@link AppSearchSchema} type
* <li>Changing the cardinality of a property to be less restrictive
* </ul>
*
* <p>The following types of schema changes are not backwards compatible:
*
* <ul>
* <li>Removal of an existing {@link AppSearchSchema} type
* <li>Removal of a property from an existing {@link AppSearchSchema} type
* <li>Changing the data type of an existing property
* <li>Changing the cardinality of a property to be more restrictive
* </ul>
*
* <p>Providing a schema with incompatible changes, will throw an {@link
* android.app.appsearch.exceptions.AppSearchException}, with a message describing the
* incompatibility. As a result, the previously set schema will remain unchanged.
*
* <p>Backward incompatible changes can be made by :
*
* <ul>
* <li>setting {@link SetSchemaRequest.Builder#setForceOverride} method to {@code true}. This
* deletes all documents that are incompatible with the new schema. The new schema is then
* saved and persisted to disk.
* <li>Add a {@link Migrator} for each incompatible type and make no deletion. The migrator will
* migrate documents from it's old schema version to the new version. Migrated types will be
* set into both {@link SetSchemaResponse#getIncompatibleTypes()} and {@link
* SetSchemaResponse#getMigratedTypes()}. See the migration section below.
* </ul>
*
* @see AppSearchSession#setSchema
* @see Migrator
*/
public final class SetSchemaRequest {
private final Set<AppSearchSchema> mSchemas;
private final Set<String> mSchemasNotDisplayedBySystem;
private final Map<String, Set<PackageIdentifier>> mSchemasVisibleToPackages;
private final Map<String, Migrator> mMigrators;
private final boolean mForceOverride;
private final int mVersion;
SetSchemaRequest(
@NonNull Set<AppSearchSchema> schemas,
@NonNull Set<String> schemasNotDisplayedBySystem,
@NonNull Map<String, Set<PackageIdentifier>> schemasVisibleToPackages,
@NonNull Map<String, Migrator> migrators,
boolean forceOverride,
int version) {
mSchemas = Objects.requireNonNull(schemas);
mSchemasNotDisplayedBySystem = Objects.requireNonNull(schemasNotDisplayedBySystem);
mSchemasVisibleToPackages = Objects.requireNonNull(schemasVisibleToPackages);
mMigrators = Objects.requireNonNull(migrators);
mForceOverride = forceOverride;
mVersion = version;
}
/** Returns the {@link AppSearchSchema} types that are part of this request. */
@NonNull
public Set<AppSearchSchema> getSchemas() {
return Collections.unmodifiableSet(mSchemas);
}
/**
* Returns all the schema types that are opted out of being displayed and visible on any system
* UI surface.
*/
@NonNull
public Set<String> getSchemasNotDisplayedBySystem() {
return Collections.unmodifiableSet(mSchemasNotDisplayedBySystem);
}
/**
* Returns a mapping of schema types to the set of packages that have access to that schema
* type.
*
* <p>It’s inefficient to call this method repeatedly.
*/
@NonNull
public Map<String, Set<PackageIdentifier>> getSchemasVisibleToPackages() {
Map<String, Set<PackageIdentifier>> copy = new ArrayMap<>();
for (String key : mSchemasVisibleToPackages.keySet()) {
copy.put(key, new ArraySet<>(mSchemasVisibleToPackages.get(key)));
}
return copy;
}
/**
* Returns the map of {@link Migrator}, the key will be the schema type of the {@link Migrator}
* associated with.
*/
@NonNull
public Map<String, Migrator> getMigrators() {
return Collections.unmodifiableMap(mMigrators);
}
/**
* Returns a mapping of {@link AppSearchSchema} types to the set of packages that have access to
* that schema type.
*
* <p>A more efficient version of {@link #getSchemasVisibleToPackages}, but it returns a
* modifiable map. This is not meant to be unhidden and should only be used by internal classes.
*
* @hide
*/
@NonNull
public Map<String, Set<PackageIdentifier>> getSchemasVisibleToPackagesInternal() {
return mSchemasVisibleToPackages;
}
/** Returns whether this request will force the schema to be overridden. */
public boolean isForceOverride() {
return mForceOverride;
}
/** Returns the database overall schema version. */
@IntRange(from = 1)
public int getVersion() {
return mVersion;
}
/** Builder for {@link SetSchemaRequest} objects. */
public static final class Builder {
private static final int DEFAULT_VERSION = 1;
private ArraySet<AppSearchSchema> mSchemas = new ArraySet<>();
private ArraySet<String> mSchemasNotDisplayedBySystem = new ArraySet<>();
private ArrayMap<String, Set<PackageIdentifier>> mSchemasVisibleToPackages =
new ArrayMap<>();
private ArrayMap<String, Migrator> mMigrators = new ArrayMap<>();
private boolean mForceOverride = false;
private int mVersion = DEFAULT_VERSION;
private boolean mBuilt = false;
/**
* Adds one or more {@link AppSearchSchema} types to the schema.
*
* <p>An {@link AppSearchSchema} object represents one type of structured data.
*
* <p>Any documents of these types will be displayed on system UI surfaces by default.
*/
@NonNull
public Builder addSchemas(@NonNull AppSearchSchema... schemas) {
Objects.requireNonNull(schemas);
resetIfBuilt();
return addSchemas(Arrays.asList(schemas));
}
/**
* Adds a collection of {@link AppSearchSchema} objects to the schema.
*
* <p>An {@link AppSearchSchema} object represents one type of structured data.
*/
@NonNull
public Builder addSchemas(@NonNull Collection<AppSearchSchema> schemas) {
Objects.requireNonNull(schemas);
resetIfBuilt();
mSchemas.addAll(schemas);
return this;
}
/**
* Sets whether or not documents from the provided {@code schemaType} will be displayed and
* visible on any system UI surface.
*
* <p>This setting applies to the provided {@code schemaType} only, and does not persist
* across {@link AppSearchSession#setSchema} calls.
*
* <p>The default behavior, if this method is not called, is to allow types to be displayed
* on system UI surfaces.
*
* @param schemaType The name of an {@link AppSearchSchema} within the same {@link
* SetSchemaRequest}, which will be configured.
* @param displayed Whether documents of this type will be displayed on system UI surfaces.
*/
// Merged list available from getSchemasNotDisplayedBySystem
@SuppressLint("MissingGetterMatchingBuilder")
@NonNull
public Builder setSchemaTypeDisplayedBySystem(
@NonNull String schemaType, boolean displayed) {
Objects.requireNonNull(schemaType);
resetIfBuilt();
if (displayed) {
mSchemasNotDisplayedBySystem.remove(schemaType);
} else {
mSchemasNotDisplayedBySystem.add(schemaType);
}
return this;
}
/**
* Sets whether or not documents from the provided {@code schemaType} can be read by the
* specified package.
*
* <p>Each package is represented by a {@link PackageIdentifier}, containing a package name
* and a byte array of type {@link android.content.pm.PackageManager#CERT_INPUT_SHA256}.
*
* <p>To opt into one-way data sharing with another application, the developer will need to
* explicitly grant the other application’s package name and certificate Read access to its
* data.
*
* <p>For two-way data sharing, both applications need to explicitly grant Read access to
* one another.
*
* <p>By default, data sharing between applications is disabled.
*
* @param schemaType The schema type to set visibility on.
* @param visible Whether the {@code schemaType} will be visible or not.
* @param packageIdentifier Represents the package that will be granted visibility.
*/
// Merged list available from getSchemasVisibleToPackages
@SuppressLint("MissingGetterMatchingBuilder")
@NonNull
public Builder setSchemaTypeVisibilityForPackage(
@NonNull String schemaType,
boolean visible,
@NonNull PackageIdentifier packageIdentifier) {
Objects.requireNonNull(schemaType);
Objects.requireNonNull(packageIdentifier);
resetIfBuilt();
Set<PackageIdentifier> packageIdentifiers = mSchemasVisibleToPackages.get(schemaType);
if (visible) {
if (packageIdentifiers == null) {
packageIdentifiers = new ArraySet<>();
}
packageIdentifiers.add(packageIdentifier);
mSchemasVisibleToPackages.put(schemaType, packageIdentifiers);
} else {
if (packageIdentifiers == null) {
// Return early since there was nothing set to begin with.
return this;
}
packageIdentifiers.remove(packageIdentifier);
if (packageIdentifiers.isEmpty()) {
// Remove the entire key so that we don't have empty sets as values.
mSchemasVisibleToPackages.remove(schemaType);
}
}
return this;
}
/**
* Sets the {@link Migrator} associated with the given SchemaType.
*
* <p>The {@link Migrator} migrates all {@link GenericDocument}s under given schema type
* from the current version number stored in AppSearch to the final version set via {@link
* #setVersion}.
*
* <p>A {@link Migrator} will be invoked if the current version number stored in AppSearch
* is different from the final version set via {@link #setVersion} and {@link
* Migrator#shouldMigrate} returns {@code true}.
*
* <p>The target schema type of the output {@link GenericDocument} of {@link
* Migrator#onUpgrade} or {@link Migrator#onDowngrade} must exist in this {@link
* SetSchemaRequest}.
*
* @param schemaType The schema type to set migrator on.
* @param migrator The migrator translates a document from its current version to the final
* version set via {@link #setVersion}.
* @see SetSchemaRequest.Builder#setVersion
* @see SetSchemaRequest.Builder#addSchemas
* @see AppSearchSession#setSchema
*/
@NonNull
@SuppressLint("MissingGetterMatchingBuilder") // Getter return plural objects.
public Builder setMigrator(@NonNull String schemaType, @NonNull Migrator migrator) {
Objects.requireNonNull(schemaType);
Objects.requireNonNull(migrator);
resetIfBuilt();
mMigrators.put(schemaType, migrator);
return this;
}
/**
* Sets a Map of {@link Migrator}s.
*
* <p>The key of the map is the schema type that the {@link Migrator} value applies to.
*
* <p>The {@link Migrator} migrates all {@link GenericDocument}s under given schema type
* from the current version number stored in AppSearch to the final version set via {@link
* #setVersion}.
*
* <p>A {@link Migrator} will be invoked if the current version number stored in AppSearch
* is different from the final version set via {@link #setVersion} and {@link
* Migrator#shouldMigrate} returns {@code true}.
*
* <p>The target schema type of the output {@link GenericDocument} of {@link
* Migrator#onUpgrade} or {@link Migrator#onDowngrade} must exist in this {@link
* SetSchemaRequest}.
*
* @param migrators A {@link Map} of migrators that translate a document from it's current
* version to the final version set via {@link #setVersion}. The key of the map is the
* schema type that the {@link Migrator} value applies to.
* @see SetSchemaRequest.Builder#setVersion
* @see SetSchemaRequest.Builder#addSchemas
* @see AppSearchSession#setSchema
*/
@NonNull
public Builder setMigrators(@NonNull Map<String, Migrator> migrators) {
Objects.requireNonNull(migrators);
resetIfBuilt();
mMigrators.putAll(migrators);
return this;
}
/**
* Sets whether or not to override the current schema in the {@link AppSearchSession}
* database.
*
* <p>Call this method whenever backward incompatible changes need to be made by setting
* {@code forceOverride} to {@code true}. As a result, during execution of the setSchema
* operation, all documents that are incompatible with the new schema will be deleted and
* the new schema will be saved and persisted.
*
* <p>By default, this is {@code false}.
*/
@NonNull
public Builder setForceOverride(boolean forceOverride) {
resetIfBuilt();
mForceOverride = forceOverride;
return this;
}
/**
* Sets the version number of the overall {@link AppSearchSchema} in the database.
*
* <p>The {@link AppSearchSession} database can only ever hold documents for one version at
* a time.
*
* <p>Setting a version number that is different from the version number currently stored in
* AppSearch will result in AppSearch calling the {@link Migrator}s provided to {@link
* AppSearchSession#setSchema} to migrate the documents already in AppSearch from the
* previous version to the one set in this request. The version number can be updated
* without any other changes to the set of schemas.
*
* <p>The version number can stay the same, increase, or decrease relative to the current
* version number that is already stored in the {@link AppSearchSession} database.
*
* <p>The version of an empty database will always be 0. You cannot set version to the
* {@link SetSchemaRequest}, if it doesn't contains any {@link AppSearchSchema}.
*
* @param version A positive integer representing the version of the entire set of schemas
* represents the version of the whole schema in the {@link AppSearchSession} database,
* default version is 1.
* @throws IllegalArgumentException if the version is negative.
* @see AppSearchSession#setSchema
* @see Migrator
* @see SetSchemaRequest.Builder#setMigrator
*/
@NonNull
public Builder setVersion(@IntRange(from = 1) int version) {
Preconditions.checkArgument(version >= 1, "Version must be a positive number.");
resetIfBuilt();
mVersion = version;
return this;
}
/**
* Builds a new {@link SetSchemaRequest} object.
*
* @throws IllegalArgumentException if schema types were referenced, but the corresponding
* {@link AppSearchSchema} type was never added.
*/
@NonNull
public SetSchemaRequest build() {
// Verify that any schema types with display or visibility settings refer to a real
// schema.
// Create a copy because we're going to remove from the set for verification purposes.
Set<String> referencedSchemas = new ArraySet<>(mSchemasNotDisplayedBySystem);
referencedSchemas.addAll(mSchemasVisibleToPackages.keySet());
for (AppSearchSchema schema : mSchemas) {
referencedSchemas.remove(schema.getSchemaType());
}
if (!referencedSchemas.isEmpty()) {
// We still have schema types that weren't seen in our mSchemas set. This means
// there wasn't a corresponding AppSearchSchema.
throw new IllegalArgumentException(
"Schema types " + referencedSchemas + " referenced, but were not added.");
}
if (mSchemas.isEmpty() && mVersion != DEFAULT_VERSION) {
throw new IllegalArgumentException(
"Cannot set version to the request if schema is empty.");
}
mBuilt = true;
return new SetSchemaRequest(
mSchemas,
mSchemasNotDisplayedBySystem,
mSchemasVisibleToPackages,
mMigrators,
mForceOverride,
mVersion);
}
private void resetIfBuilt() {
if (mBuilt) {
ArrayMap<String, Set<PackageIdentifier>> schemasVisibleToPackages =
new ArrayMap<>(mSchemasVisibleToPackages.size());
for (Map.Entry<String, Set<PackageIdentifier>> entry :
mSchemasVisibleToPackages.entrySet()) {
schemasVisibleToPackages.put(entry.getKey(), new ArraySet<>(entry.getValue()));
}
mSchemasVisibleToPackages = schemasVisibleToPackages;
mSchemas = new ArraySet<>(mSchemas);
mSchemasNotDisplayedBySystem = new ArraySet<>(mSchemasNotDisplayedBySystem);
mMigrators = new ArrayMap<>(mMigrators);
mBuilt = false;
}
}
}
}

View File

@@ -1,381 +0,0 @@
/*
* Copyright 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 android.app.appsearch;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;
import android.util.ArraySet;
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
/** The response class of {@link AppSearchSession#setSchema} */
public class SetSchemaResponse {
private static final String DELETED_TYPES_FIELD = "deletedTypes";
private static final String INCOMPATIBLE_TYPES_FIELD = "incompatibleTypes";
private static final String MIGRATED_TYPES_FIELD = "migratedTypes";
private final Bundle mBundle;
/**
* The migrationFailures won't be saved in the bundle. Since:
*
* <ul>
* <li>{@link MigrationFailure} is generated in {@link AppSearchSession} which will be the SDK
* side in platform. We don't need to pass it from service side via binder.
* <li>Translate multiple {@link MigrationFailure}s to bundles in {@link Builder} and then
* back in constructor will be a huge waste.
* </ul>
*/
private final List<MigrationFailure> mMigrationFailures;
/** Cache of the inflated deleted schema types. Comes from inflating mBundles at first use. */
@Nullable private Set<String> mDeletedTypes;
/** Cache of the inflated migrated schema types. Comes from inflating mBundles at first use. */
@Nullable private Set<String> mMigratedTypes;
/**
* Cache of the inflated incompatible schema types. Comes from inflating mBundles at first use.
*/
@Nullable private Set<String> mIncompatibleTypes;
SetSchemaResponse(@NonNull Bundle bundle, @NonNull List<MigrationFailure> migrationFailures) {
mBundle = Objects.requireNonNull(bundle);
mMigrationFailures = Objects.requireNonNull(migrationFailures);
}
SetSchemaResponse(@NonNull Bundle bundle) {
this(bundle, /*migrationFailures=*/ Collections.emptyList());
}
/**
* Returns the {@link Bundle} populated by this builder.
*
* @hide
*/
@NonNull
public Bundle getBundle() {
return mBundle;
}
/**
* Returns a {@link List} of all failed {@link MigrationFailure}.
*
* <p>A {@link MigrationFailure} will be generated if the system trying to save a post-migrated
* {@link GenericDocument} but fail.
*
* <p>{@link MigrationFailure} contains the namespace, id and schemaType of the post-migrated
* {@link GenericDocument} and the error reason. Mostly it will be mismatch the schema it
* migrated to.
*/
@NonNull
public List<MigrationFailure> getMigrationFailures() {
return Collections.unmodifiableList(mMigrationFailures);
}
/**
* Returns a {@link Set} of deleted schema types.
*
* <p>A "deleted" type is a schema type that was previously a part of the database schema but
* was not present in the {@link SetSchemaRequest} object provided in the
* {@link AppSearchSession#setSchema) call.
*
* <p>Documents for a deleted type are removed from the database.
*/
@NonNull
public Set<String> getDeletedTypes() {
if (mDeletedTypes == null) {
mDeletedTypes =
new ArraySet<>(
Objects.requireNonNull(
mBundle.getStringArrayList(DELETED_TYPES_FIELD)));
}
return Collections.unmodifiableSet(mDeletedTypes);
}
/**
* Returns a {@link Set} of schema type that were migrated by the {@link
* AppSearchSession#setSchema} call.
*
* <p>A "migrated" type is a schema type that has triggered a {@link Migrator} instance to
* migrate documents of the schema type to another schema type, or to another version of the
* schema type.
*
* <p>If a document fails to be migrated, a {@link MigrationFailure} will be generated for that
* document.
*
* @see Migrator
*/
@NonNull
public Set<String> getMigratedTypes() {
if (mMigratedTypes == null) {
mMigratedTypes =
new ArraySet<>(
Objects.requireNonNull(
mBundle.getStringArrayList(MIGRATED_TYPES_FIELD)));
}
return Collections.unmodifiableSet(mMigratedTypes);
}
/**
* Returns a {@link Set} of schema type whose new definitions set in the {@link
* AppSearchSession#setSchema} call were incompatible with the pre-existing schema.
*
* <p>If a {@link Migrator} is provided for this type and the migration is success triggered.
* The type will also appear in {@link #getMigratedTypes()}.
*
* @see SetSchemaRequest
* @see AppSearchSession#setSchema
* @see SetSchemaRequest.Builder#setForceOverride
*/
@NonNull
public Set<String> getIncompatibleTypes() {
if (mIncompatibleTypes == null) {
mIncompatibleTypes =
new ArraySet<>(
Objects.requireNonNull(
mBundle.getStringArrayList(INCOMPATIBLE_TYPES_FIELD)));
}
return Collections.unmodifiableSet(mIncompatibleTypes);
}
/**
* Translates the {@link SetSchemaResponse}'s bundle to {@link Builder}.
*
* @hide
*/
@NonNull
// TODO(b/179302942) change to Builder(mBundle) powered by mBundle.deepCopy
public Builder toBuilder() {
return new Builder()
.addDeletedTypes(getDeletedTypes())
.addIncompatibleTypes(getIncompatibleTypes())
.addMigratedTypes(getMigratedTypes())
.addMigrationFailures(mMigrationFailures);
}
/** Builder for {@link SetSchemaResponse} objects. */
public static final class Builder {
private List<MigrationFailure> mMigrationFailures = new ArrayList<>();
private ArrayList<String> mDeletedTypes = new ArrayList<>();
private ArrayList<String> mMigratedTypes = new ArrayList<>();
private ArrayList<String> mIncompatibleTypes = new ArrayList<>();
private boolean mBuilt = false;
/** Adds {@link MigrationFailure}s to the list of migration failures. */
@NonNull
public Builder addMigrationFailures(
@NonNull Collection<MigrationFailure> migrationFailures) {
Objects.requireNonNull(migrationFailures);
resetIfBuilt();
mMigrationFailures.addAll(migrationFailures);
return this;
}
/** Adds a {@link MigrationFailure} to the list of migration failures. */
@NonNull
public Builder addMigrationFailure(@NonNull MigrationFailure migrationFailure) {
Objects.requireNonNull(migrationFailure);
resetIfBuilt();
mMigrationFailures.add(migrationFailure);
return this;
}
/** Adds deletedTypes to the list of deleted schema types. */
@NonNull
public Builder addDeletedTypes(@NonNull Collection<String> deletedTypes) {
Objects.requireNonNull(deletedTypes);
resetIfBuilt();
mDeletedTypes.addAll(deletedTypes);
return this;
}
/** Adds one deletedType to the list of deleted schema types. */
@NonNull
public Builder addDeletedType(@NonNull String deletedType) {
Objects.requireNonNull(deletedType);
resetIfBuilt();
mDeletedTypes.add(deletedType);
return this;
}
/** Adds incompatibleTypes to the list of incompatible schema types. */
@NonNull
public Builder addIncompatibleTypes(@NonNull Collection<String> incompatibleTypes) {
Objects.requireNonNull(incompatibleTypes);
resetIfBuilt();
mIncompatibleTypes.addAll(incompatibleTypes);
return this;
}
/** Adds one incompatibleType to the list of incompatible schema types. */
@NonNull
public Builder addIncompatibleType(@NonNull String incompatibleType) {
Objects.requireNonNull(incompatibleType);
resetIfBuilt();
mIncompatibleTypes.add(incompatibleType);
return this;
}
/** Adds migratedTypes to the list of migrated schema types. */
@NonNull
public Builder addMigratedTypes(@NonNull Collection<String> migratedTypes) {
Objects.requireNonNull(migratedTypes);
resetIfBuilt();
mMigratedTypes.addAll(migratedTypes);
return this;
}
/** Adds one migratedType to the list of migrated schema types. */
@NonNull
public Builder addMigratedType(@NonNull String migratedType) {
Objects.requireNonNull(migratedType);
resetIfBuilt();
mMigratedTypes.add(migratedType);
return this;
}
/** Builds a {@link SetSchemaResponse} object. */
@NonNull
public SetSchemaResponse build() {
Bundle bundle = new Bundle();
bundle.putStringArrayList(INCOMPATIBLE_TYPES_FIELD, mIncompatibleTypes);
bundle.putStringArrayList(DELETED_TYPES_FIELD, mDeletedTypes);
bundle.putStringArrayList(MIGRATED_TYPES_FIELD, mMigratedTypes);
mBuilt = true;
// Avoid converting the potential thousands of MigrationFailures to Pracelable and
// back just for put in bundle. In platform, we should set MigrationFailures in
// AppSearchSession after we pass SetSchemaResponse via binder.
return new SetSchemaResponse(bundle, mMigrationFailures);
}
private void resetIfBuilt() {
if (mBuilt) {
mMigrationFailures = new ArrayList<>(mMigrationFailures);
mDeletedTypes = new ArrayList<>(mDeletedTypes);
mMigratedTypes = new ArrayList<>(mMigratedTypes);
mIncompatibleTypes = new ArrayList<>(mIncompatibleTypes);
mBuilt = false;
}
}
}
/**
* The class represents a post-migrated {@link GenericDocument} that failed to be saved by
* {@link AppSearchSession#setSchema}.
*/
public static class MigrationFailure {
private static final String SCHEMA_TYPE_FIELD = "schemaType";
private static final String NAMESPACE_FIELD = "namespace";
private static final String DOCUMENT_ID_FIELD = "id";
private static final String ERROR_MESSAGE_FIELD = "errorMessage";
private static final String RESULT_CODE_FIELD = "resultCode";
private final Bundle mBundle;
/**
* Constructs a new {@link MigrationFailure}.
*
* @param namespace The namespace of the document which failed to be migrated.
* @param documentId The id of the document which failed to be migrated.
* @param schemaType The type of the document which failed to be migrated.
* @param failedResult The reason why the document failed to be indexed.
* @throws IllegalArgumentException if the provided {@code failedResult} was not a failure.
*/
public MigrationFailure(
@NonNull String namespace,
@NonNull String documentId,
@NonNull String schemaType,
@NonNull AppSearchResult<?> failedResult) {
mBundle = new Bundle();
mBundle.putString(NAMESPACE_FIELD, Objects.requireNonNull(namespace));
mBundle.putString(DOCUMENT_ID_FIELD, Objects.requireNonNull(documentId));
mBundle.putString(SCHEMA_TYPE_FIELD, Objects.requireNonNull(schemaType));
Objects.requireNonNull(failedResult);
Preconditions.checkArgument(
!failedResult.isSuccess(), "failedResult was actually successful");
mBundle.putString(ERROR_MESSAGE_FIELD, failedResult.getErrorMessage());
mBundle.putInt(RESULT_CODE_FIELD, failedResult.getResultCode());
}
MigrationFailure(@NonNull Bundle bundle) {
mBundle = Objects.requireNonNull(bundle);
}
/**
* Returns the Bundle of the {@link MigrationFailure}.
*
* @hide
*/
@NonNull
public Bundle getBundle() {
return mBundle;
}
/** Returns the namespace of the {@link GenericDocument} that failed to be migrated. */
@NonNull
public String getNamespace() {
return mBundle.getString(NAMESPACE_FIELD, /*defaultValue=*/ "");
}
/** Returns the id of the {@link GenericDocument} that failed to be migrated. */
@NonNull
public String getDocumentId() {
return mBundle.getString(DOCUMENT_ID_FIELD, /*defaultValue=*/ "");
}
/** Returns the schema type of the {@link GenericDocument} that failed to be migrated. */
@NonNull
public String getSchemaType() {
return mBundle.getString(SCHEMA_TYPE_FIELD, /*defaultValue=*/ "");
}
/**
* Returns the {@link AppSearchResult} that indicates why the post-migration {@link
* GenericDocument} failed to be indexed.
*/
@NonNull
public AppSearchResult<Void> getAppSearchResult() {
return AppSearchResult.newFailedResult(
mBundle.getInt(RESULT_CODE_FIELD),
mBundle.getString(ERROR_MESSAGE_FIELD, /*defaultValue=*/ ""));
}
@NonNull
@Override
public String toString() {
return "MigrationFailure { schemaType: "
+ getSchemaType()
+ ", namespace: "
+ getNamespace()
+ ", documentId: "
+ getDocumentId()
+ ", appSearchResult: "
+ getAppSearchResult().toString()
+ "}";
}
}
}

View File

@@ -1,110 +0,0 @@
/*
* Copyright 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 android.app.appsearch;
import android.annotation.NonNull;
import android.os.Bundle;
import java.util.Objects;
/** The response class of {@code AppSearchSession#getStorageInfo}. */
public class StorageInfo {
private static final String SIZE_BYTES_FIELD = "sizeBytes";
private static final String ALIVE_DOCUMENTS_COUNT = "aliveDocumentsCount";
private static final String ALIVE_NAMESPACES_COUNT = "aliveNamespacesCount";
private final Bundle mBundle;
StorageInfo(@NonNull Bundle bundle) {
mBundle = Objects.requireNonNull(bundle);
}
/**
* Returns the {@link Bundle} populated by this builder.
*
* @hide
*/
@NonNull
public Bundle getBundle() {
return mBundle;
}
/** Returns the estimated size of the session's database in bytes. */
public long getSizeBytes() {
return mBundle.getLong(SIZE_BYTES_FIELD);
}
/**
* Returns the number of alive documents in the current session.
*
* <p>Alive documents are documents that haven't been deleted and haven't exceeded the ttl as
* set in {@link GenericDocument.Builder#setTtlMillis}.
*/
public int getAliveDocumentsCount() {
return mBundle.getInt(ALIVE_DOCUMENTS_COUNT);
}
/**
* Returns the number of namespaces that have at least one alive document in the current
* session's database.
*
* <p>Alive documents are documents that haven't been deleted and haven't exceeded the ttl as
* set in {@link GenericDocument.Builder#setTtlMillis}.
*/
public int getAliveNamespacesCount() {
return mBundle.getInt(ALIVE_NAMESPACES_COUNT);
}
/** Builder for {@link StorageInfo} objects. */
public static final class Builder {
private long mSizeBytes;
private int mAliveDocumentsCount;
private int mAliveNamespacesCount;
/** Sets the size in bytes. */
@NonNull
public StorageInfo.Builder setSizeBytes(long sizeBytes) {
mSizeBytes = sizeBytes;
return this;
}
/** Sets the number of alive documents. */
@NonNull
public StorageInfo.Builder setAliveDocumentsCount(int aliveDocumentsCount) {
mAliveDocumentsCount = aliveDocumentsCount;
return this;
}
/** Sets the number of alive namespaces. */
@NonNull
public StorageInfo.Builder setAliveNamespacesCount(int aliveNamespacesCount) {
mAliveNamespacesCount = aliveNamespacesCount;
return this;
}
/** Builds a {@link StorageInfo} object. */
@NonNull
public StorageInfo build() {
Bundle bundle = new Bundle();
bundle.putLong(SIZE_BYTES_FIELD, mSizeBytes);
bundle.putInt(ALIVE_DOCUMENTS_COUNT, mAliveDocumentsCount);
bundle.putInt(ALIVE_NAMESPACES_COUNT, mAliveNamespacesCount);
return new StorageInfo(bundle);
}
}
}

View File

@@ -1,85 +0,0 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch.exceptions;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.appsearch.AppSearchResult;
/**
* An exception thrown by {@link android.app.appsearch.AppSearchSession} or a subcomponent.
*
* <p>These exceptions can be converted into a failed {@link AppSearchResult} for propagating to the
* client.
*/
public class AppSearchException extends Exception {
private final @AppSearchResult.ResultCode int mResultCode;
/**
* Initializes an {@link AppSearchException} with no message.
*
* @param resultCode One of the constants documented in {@link AppSearchResult#getResultCode}.
*/
public AppSearchException(@AppSearchResult.ResultCode int resultCode) {
this(resultCode, /*message=*/ null);
}
/**
* Initializes an {@link AppSearchException} with a result code and message.
*
* @param resultCode One of the constants documented in {@link AppSearchResult#getResultCode}.
* @param message The detail message (which is saved for later retrieval by the {@link
* #getMessage()} method).
*/
public AppSearchException(
@AppSearchResult.ResultCode int resultCode, @Nullable String message) {
this(resultCode, message, /*cause=*/ null);
}
/**
* Initializes an {@link AppSearchException} with a result code, message and cause.
*
* @param resultCode One of the constants documented in {@link AppSearchResult#getResultCode}.
* @param message The detail message (which is saved for later retrieval by the {@link
* #getMessage()} method).
* @param cause The cause (which is saved for later retrieval by the {@link #getCause()}
* method). (A null value is permitted, and indicates that the cause is nonexistent or
* unknown.)
*/
public AppSearchException(
@AppSearchResult.ResultCode int resultCode,
@Nullable String message,
@Nullable Throwable cause) {
super(message, cause);
mResultCode = resultCode;
}
/**
* Returns the result code this exception was constructed with.
*
* @return One of the constants documented in {@link AppSearchResult#getResultCode}.
*/
public @AppSearchResult.ResultCode int getResultCode() {
return mResultCode;
}
/** Converts this {@link java.lang.Exception} into a failed {@link AppSearchResult}. */
@NonNull
public <T> AppSearchResult<T> toAppSearchResult() {
return AppSearchResult.newFailedResult(mResultCode, getMessage());
}
}

View File

@@ -1,36 +0,0 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch.exceptions;
import android.annotation.NonNull;
/**
* Indicates that a {@link android.app.appsearch.AppSearchSchema} has logical inconsistencies such
* as unpopulated mandatory fields or illegal combinations of parameters.
*
* @hide
*/
public class IllegalSchemaException extends IllegalArgumentException {
/**
* Constructs a new {@link IllegalSchemaException}.
*
* @param message A developer-readable description of the issue with the bundle.
*/
public IllegalSchemaException(@NonNull String message) {
super(message);
}
}

View File

@@ -1,251 +0,0 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch.util;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;
import android.os.Parcel;
import android.util.SparseArray;
import java.util.ArrayList;
import java.util.Arrays;
/**
* Utilities for working with {@link android.os.Bundle}.
*
* @hide
*/
public final class BundleUtil {
private BundleUtil() {}
/**
* Deeply checks two bundles are equal or not.
*
* <p>Two bundles will be considered equal if they contain the same keys, and each value is also
* equal. Bundle values are compared using deepEquals.
*/
public static boolean deepEquals(@Nullable Bundle one, @Nullable Bundle two) {
if (one == null && two == null) {
return true;
}
if (one == null || two == null) {
return false;
}
if (one.size() != two.size()) {
return false;
}
if (!one.keySet().equals(two.keySet())) {
return false;
}
// Bundle inherit its equals() from Object.java, which only compare their memory address.
// We should iterate all keys and check their presents and values in both bundle.
for (String key : one.keySet()) {
if (!bundleValueEquals(one.get(key), two.get(key))) {
return false;
}
}
return true;
}
/**
* Deeply checks whether two values in a Bundle are equal or not.
*
* <p>Values of type Bundle are compared using {@link #deepEquals}.
*/
private static boolean bundleValueEquals(@Nullable Object one, @Nullable Object two) {
if (one == null && two == null) {
return true;
}
if (one == null || two == null) {
return false;
}
if (one.equals(two)) {
return true;
}
if (one instanceof Bundle && two instanceof Bundle) {
return deepEquals((Bundle) one, (Bundle) two);
} else if (one instanceof int[] && two instanceof int[]) {
return Arrays.equals((int[]) one, (int[]) two);
} else if (one instanceof byte[] && two instanceof byte[]) {
return Arrays.equals((byte[]) one, (byte[]) two);
} else if (one instanceof char[] && two instanceof char[]) {
return Arrays.equals((char[]) one, (char[]) two);
} else if (one instanceof long[] && two instanceof long[]) {
return Arrays.equals((long[]) one, (long[]) two);
} else if (one instanceof float[] && two instanceof float[]) {
return Arrays.equals((float[]) one, (float[]) two);
} else if (one instanceof short[] && two instanceof short[]) {
return Arrays.equals((short[]) one, (short[]) two);
} else if (one instanceof double[] && two instanceof double[]) {
return Arrays.equals((double[]) one, (double[]) two);
} else if (one instanceof boolean[] && two instanceof boolean[]) {
return Arrays.equals((boolean[]) one, (boolean[]) two);
} else if (one instanceof Object[] && two instanceof Object[]) {
Object[] arrayOne = (Object[]) one;
Object[] arrayTwo = (Object[]) two;
if (arrayOne.length != arrayTwo.length) {
return false;
}
if (Arrays.equals(arrayOne, arrayTwo)) {
return true;
}
for (int i = 0; i < arrayOne.length; i++) {
if (!bundleValueEquals(arrayOne[i], arrayTwo[i])) {
return false;
}
}
return true;
} else if (one instanceof ArrayList && two instanceof ArrayList) {
ArrayList<?> listOne = (ArrayList<?>) one;
ArrayList<?> listTwo = (ArrayList<?>) two;
if (listOne.size() != listTwo.size()) {
return false;
}
for (int i = 0; i < listOne.size(); i++) {
if (!bundleValueEquals(listOne.get(i), listTwo.get(i))) {
return false;
}
}
return true;
} else if (one instanceof SparseArray && two instanceof SparseArray) {
SparseArray<?> arrayOne = (SparseArray<?>) one;
SparseArray<?> arrayTwo = (SparseArray<?>) two;
if (arrayOne.size() != arrayTwo.size()) {
return false;
}
for (int i = 0; i < arrayOne.size(); i++) {
if (arrayOne.keyAt(i) != arrayTwo.keyAt(i)
|| !bundleValueEquals(arrayOne.valueAt(i), arrayTwo.valueAt(i))) {
return false;
}
}
return true;
}
return false;
}
/**
* Calculates the hash code for a bundle.
*
* <p>The hash code is only effected by the contents in the bundle. Bundles will get consistent
* hash code if they have same contents.
*/
public static int deepHashCode(@Nullable Bundle bundle) {
if (bundle == null) {
return 0;
}
int[] hashCodes = new int[bundle.size() + 1];
int hashCodeIdx = 0;
// Bundle inherit its hashCode() from Object.java, which only relative to their memory
// address. Bundle doesn't have an order, so we should iterate all keys and combine
// their value's hashcode into an array. And use the hashcode of the array to be
// the hashcode of the bundle.
// Because bundle.keySet() doesn't guarantee any particular order, we need to sort the keys
// in case the iteration order varies from run to run.
String[] keys = bundle.keySet().toArray(new String[0]);
Arrays.sort(keys);
// Hash the keys so we can detect key-only differences
hashCodes[hashCodeIdx++] = Arrays.hashCode(keys);
for (int keyIdx = 0; keyIdx < keys.length; keyIdx++) {
Object value = bundle.get(keys[keyIdx]);
if (value instanceof Bundle) {
hashCodes[hashCodeIdx++] = deepHashCode((Bundle) value);
} else if (value instanceof int[]) {
hashCodes[hashCodeIdx++] = Arrays.hashCode((int[]) value);
} else if (value instanceof byte[]) {
hashCodes[hashCodeIdx++] = Arrays.hashCode((byte[]) value);
} else if (value instanceof char[]) {
hashCodes[hashCodeIdx++] = Arrays.hashCode((char[]) value);
} else if (value instanceof long[]) {
hashCodes[hashCodeIdx++] = Arrays.hashCode((long[]) value);
} else if (value instanceof float[]) {
hashCodes[hashCodeIdx++] = Arrays.hashCode((float[]) value);
} else if (value instanceof short[]) {
hashCodes[hashCodeIdx++] = Arrays.hashCode((short[]) value);
} else if (value instanceof double[]) {
hashCodes[hashCodeIdx++] = Arrays.hashCode((double[]) value);
} else if (value instanceof boolean[]) {
hashCodes[hashCodeIdx++] = Arrays.hashCode((boolean[]) value);
} else if (value instanceof String[]) {
// Optimization to avoid Object[] handler creating an inner array for common cases
hashCodes[hashCodeIdx++] = Arrays.hashCode((String[]) value);
} else if (value instanceof Object[]) {
Object[] array = (Object[]) value;
int[] innerHashCodes = new int[array.length];
for (int j = 0; j < array.length; j++) {
if (array[j] instanceof Bundle) {
innerHashCodes[j] = deepHashCode((Bundle) array[j]);
} else if (array[j] != null) {
innerHashCodes[j] = array[j].hashCode();
}
}
hashCodes[hashCodeIdx++] = Arrays.hashCode(innerHashCodes);
} else if (value instanceof ArrayList) {
ArrayList<?> list = (ArrayList<?>) value;
int[] innerHashCodes = new int[list.size()];
for (int j = 0; j < innerHashCodes.length; j++) {
Object item = list.get(j);
if (item instanceof Bundle) {
innerHashCodes[j] = deepHashCode((Bundle) item);
} else if (item != null) {
innerHashCodes[j] = item.hashCode();
}
}
hashCodes[hashCodeIdx++] = Arrays.hashCode(innerHashCodes);
} else if (value instanceof SparseArray) {
SparseArray<?> array = (SparseArray<?>) value;
int[] innerHashCodes = new int[array.size() * 2];
for (int j = 0; j < array.size(); j++) {
innerHashCodes[j * 2] = array.keyAt(j);
Object item = array.valueAt(j);
if (item instanceof Bundle) {
innerHashCodes[j * 2 + 1] = deepHashCode((Bundle) item);
} else if (item != null) {
innerHashCodes[j * 2 + 1] = item.hashCode();
}
}
hashCodes[hashCodeIdx++] = Arrays.hashCode(innerHashCodes);
} else {
hashCodes[hashCodeIdx++] = value.hashCode();
}
}
return Arrays.hashCode(hashCodes);
}
/**
* Deeply clones a Bundle.
*
* <p>Values which are Bundles, Lists or Arrays are deeply copied themselves.
*/
@NonNull
public static Bundle deepCopy(@NonNull Bundle bundle) {
// Write bundle to bytes
Parcel parcel = Parcel.obtain();
try {
parcel.writeBundle(bundle);
byte[] serializedMessage = parcel.marshall();
// Read bundle from bytes
parcel.unmarshall(serializedMessage, 0, serializedMessage.length);
parcel.setDataPosition(0);
return parcel.readBundle();
} finally {
parcel.recycle();
}
}
}

View File

@@ -1,126 +0,0 @@
/*
* Copyright 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 android.app.appsearch.util;
import android.annotation.NonNull;
/**
* Utility for building indented strings.
*
* <p>This is a wrapper for {@link StringBuilder} for appending strings with indentation. The
* indentation level can be increased by calling {@link #increaseIndentLevel()} and decreased by
* calling {@link #decreaseIndentLevel()}.
*
* <p>Indentation is applied after each newline character for the given indent level.
*
* @hide
*/
public class IndentingStringBuilder {
private final StringBuilder mStringBuilder = new StringBuilder();
// Indicates whether next non-newline character should have an indent applied before it.
private boolean mIndentNext = false;
private int mIndentLevel = 0;
/** Increases the indent level by one for appended strings. */
@NonNull
public IndentingStringBuilder increaseIndentLevel() {
mIndentLevel++;
return this;
}
/** Decreases the indent level by one for appended strings. */
@NonNull
public IndentingStringBuilder decreaseIndentLevel() throws IllegalStateException {
if (mIndentLevel == 0) {
throw new IllegalStateException("Cannot set indent level below 0.");
}
mIndentLevel--;
return this;
}
/**
* Appends provided {@code String} at the current indentation level.
*
* <p>Indentation is applied after each newline character.
*/
@NonNull
public IndentingStringBuilder append(@NonNull String str) {
applyIndentToString(str);
return this;
}
/**
* Appends provided {@code Object}, represented as a {@code String}, at the current indentation
* level.
*
* <p>Indentation is applied after each newline character.
*/
@NonNull
public IndentingStringBuilder append(@NonNull Object obj) {
applyIndentToString(obj.toString());
return this;
}
@Override
@NonNull
public String toString() {
return mStringBuilder.toString();
}
/** Adds indent string to the {@link StringBuilder} instance for current indent level. */
private void applyIndent() {
for (int i = 0; i < mIndentLevel; i++) {
mStringBuilder.append(" ");
}
}
/**
* Applies indent, for current indent level, after each newline character.
*
* <p>Consecutive newline characters are not indented.
*/
private void applyIndentToString(@NonNull String str) {
int index = str.indexOf("\n");
if (index == 0) {
// String begins with new line character: append newline and slide past newline.
mStringBuilder.append("\n");
mIndentNext = true;
if (str.length() > 1) {
applyIndentToString(str.substring(index + 1));
}
} else if (index >= 1) {
// String contains new line character: divide string between newline, append new line,
// and recurse on each string.
String beforeIndentString = str.substring(0, index);
applyIndentToString(beforeIndentString);
mStringBuilder.append("\n");
mIndentNext = true;
if (str.length() > index + 1) {
String afterIndentString = str.substring(index + 1);
applyIndentToString(afterIndentString);
}
} else {
// String does not contain newline character: append string.
if (mIndentNext) {
applyIndent();
mIndentNext = false;
}
mStringBuilder.append(str);
}
}
}

View File

@@ -1,104 +0,0 @@
/*
* Copyright 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 android.app.appsearch.util;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.Log;
import java.util.Objects;
/**
* Utilities for logging to logcat.
*
* @hide
*/
public final class LogUtil {
/**
* The {@link #piiTrace} logs are intended for sensitive data that can't be enabled in
* production, so they are build-gated by this constant.
*
* <p>
*
* <ul>
* <li>0: no tracing.
* <li>1: fast tracing (statuses/counts only)
* <li>2: full tracing (complete messages)
* </ul>
*/
private static final int PII_TRACE_LEVEL = 0;
private final String mTag;
public LogUtil(@NonNull String tag) {
mTag = Objects.requireNonNull(tag);
}
/** Returns whether piiTrace() is enabled (PII_TRACE_LEVEL > 0). */
public boolean isPiiTraceEnabled() {
return PII_TRACE_LEVEL > 0;
}
/**
* If icing lib interaction tracing is enabled via {@link #PII_TRACE_LEVEL}, logs the provided
* message to logcat.
*
* <p>If {@link #PII_TRACE_LEVEL} is 0, nothing is logged and this method returns immediately.
*/
public void piiTrace(@NonNull String message) {
piiTrace(message, /*fastTraceObj=*/ null, /*fullTraceObj=*/ null);
}
/**
* If icing lib interaction tracing is enabled via {@link #PII_TRACE_LEVEL}, logs the provided
* message and object to logcat.
*
* <p>If {@link #PII_TRACE_LEVEL} is 0, nothing is logged and this method returns immediately.
*
* <p>Otherwise, {@code traceObj} is logged if it is non-null.
*/
public void piiTrace(@NonNull String message, @Nullable Object traceObj) {
piiTrace(message, /*fastTraceObj=*/ traceObj, /*fullTraceObj=*/ null);
}
/**
* If icing lib interaction tracing is enabled via {@link #PII_TRACE_LEVEL}, logs the provided
* message and objects to logcat.
*
* <p>If {@link #PII_TRACE_LEVEL} is 0, nothing is logged and this method returns immediately.
*
* <p>If {@link #PII_TRACE_LEVEL} is 1, {@code fastTraceObj} is logged if it is non-null.
*
* <p>If {@link #PII_TRACE_LEVEL} is 2, {@code fullTraceObj} is logged if it is non-null, else
* {@code fastTraceObj} is logged if it is non-null..
*/
public void piiTrace(
@NonNull String message, @Nullable Object fastTraceObj, @Nullable Object fullTraceObj) {
if (PII_TRACE_LEVEL == 0) {
return;
}
StringBuilder builder = new StringBuilder("(trace) ").append(message);
if (PII_TRACE_LEVEL == 1 && fastTraceObj != null) {
builder.append(": ").append(fastTraceObj);
} else if (PII_TRACE_LEVEL == 2 && fullTraceObj != null) {
builder.append(": ").append(fullTraceObj);
} else if (PII_TRACE_LEVEL == 2 && fastTraceObj != null) {
builder.append(": ").append(fastTraceObj);
}
Log.i(mTag, builder.toString());
}
}

View File

@@ -1,108 +0,0 @@
/*
* Copyright 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 android.app.appsearch.util;
import android.annotation.NonNull;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.Migrator;
import android.app.appsearch.SetSchemaResponse;
import android.app.appsearch.exceptions.AppSearchException;
import android.util.ArrayMap;
import android.util.ArraySet;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
/**
* Utilities for schema migration.
*
* @hide
*/
public final class SchemaMigrationUtil {
private SchemaMigrationUtil() {}
/**
* Returns all active {@link Migrator}s that need to be triggered in this migration.
*
* <p>{@link Migrator#shouldMigrate} returns {@code true} will make the {@link Migrator} active.
*/
@NonNull
public static Map<String, Migrator> getActiveMigrators(
@NonNull Set<AppSearchSchema> existingSchemas,
@NonNull Map<String, Migrator> migrators,
int currentVersion,
int finalVersion) {
if (currentVersion == finalVersion) {
return Collections.emptyMap();
}
Set<String> existingTypes = new ArraySet<>(existingSchemas.size());
for (AppSearchSchema schema : existingSchemas) {
existingTypes.add(schema.getSchemaType());
}
Map<String, Migrator> activeMigrators = new ArrayMap<>();
for (Map.Entry<String, Migrator> entry : migrators.entrySet()) {
// The device contains the source type, and we should trigger migration for the type.
String schemaType = entry.getKey();
Migrator migrator = entry.getValue();
if (existingTypes.contains(schemaType)
&& migrator.shouldMigrate(currentVersion, finalVersion)) {
activeMigrators.put(schemaType, migrator);
}
}
return activeMigrators;
}
/**
* Checks the setSchema() call won't delete any types or has incompatible types after all {@link
* Migrator} has been triggered..
*/
public static void checkDeletedAndIncompatibleAfterMigration(
@NonNull SetSchemaResponse setSchemaResponse, @NonNull Set<String> activeMigrators)
throws AppSearchException {
Set<String> unmigratedIncompatibleTypes =
new ArraySet<>(setSchemaResponse.getIncompatibleTypes());
unmigratedIncompatibleTypes.removeAll(activeMigrators);
Set<String> unmigratedDeletedTypes = new ArraySet<>(setSchemaResponse.getDeletedTypes());
unmigratedDeletedTypes.removeAll(activeMigrators);
// check if there are any unmigrated incompatible types or deleted types. If there
// are, we will getActiveMigratorsthrow an exception. That's the only case we
// swallowed in the AppSearchImpl#setSchema().
// Since the force override is false, the schema will not have been set if there are
// any incompatible or deleted types.
checkDeletedAndIncompatible(unmigratedDeletedTypes, unmigratedIncompatibleTypes);
}
/** Checks the setSchema() call won't delete any types or has incompatible types. */
public static void checkDeletedAndIncompatible(
@NonNull Set<String> deletedTypes, @NonNull Set<String> incompatibleTypes)
throws AppSearchException {
if (deletedTypes.size() > 0 || incompatibleTypes.size() > 0) {
String newMessage =
"Schema is incompatible."
+ "\n Deleted types: "
+ deletedTypes
+ "\n Incompatible types: "
+ incompatibleTypes;
throw new AppSearchException(AppSearchResult.RESULT_INVALID_SCHEMA, newMessage);
}
}
}

View File

@@ -1,69 +0,0 @@
// Copyright (C) 2019 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package {
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_license"],
}
genrule {
name: "statslog-appsearch-java-gen",
tools: ["stats-log-api-gen"],
cmd: "$(location stats-log-api-gen) --java $(out) --module appsearch --javaPackage com.android.server.appsearch.stats --javaClass AppSearchStatsLog",
out: ["com/android/server/appsearch/stats/AppSearchStatsLog.java"],
}
java_library {
name: "statslog-appsearch-lib",
srcs: [":statslog-appsearch-java-gen"],
libs: [
"framework-statsd.stubs.module_lib",
],
sdk_version: "system_server_current",
apex_available: ["com.android.appsearch"],
}
java_library {
name: "service-appsearch",
srcs: ["java/**/*.java"],
sdk_version: "system_server_current",
static_libs: [
"icing-java-proto-lite",
"libicing-java",
"statslog-appsearch-lib",
// Entries below this line are outside of the appsearch package tree and must be kept in
// sync with jarjar.txt
"modules-utils-preconditions",
],
libs: [
"framework-appsearch.impl",
"framework-statsd.stubs.module_lib",
],
defaults: ["framework-system-server-module-defaults"],
permitted_packages: [
"com.android.server.appsearch",
"com.google.android.icing",
],
jarjar_rules: "jarjar-rules.txt",
visibility: [
// These are required until appsearch is properly unbundled.
"//frameworks/base/services/tests/mockingservicestests",
"//frameworks/base/services/tests/servicestests",
],
apex_available: ["com.android.appsearch"],
installable: true,
}

View File

@@ -1,12 +0,0 @@
# Rename all icing classes to match our module name. OEMs could start using icing lib for some other
# purpose in system service, which would cause class collisions when loading our apex into the
# system service.
rule com.google.protobuf.** com.android.server.appsearch.protobuf.@1
rule com.google.android.icing.proto.** com.android.server.appsearch.icing.proto.@1
# Rename all com.android.internal.util classes to prevent class name collisions
# between this module and the other versions of the utility classes linked into
# the framework.
# These must be kept in sync with the sources of framework-utils-appsearch
rule com.android.internal.util.Preconditions* com.android.server.appsearch.internal.util.Preconditions@1

View File

@@ -1,454 +0,0 @@
/*
* 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;
import android.annotation.NonNull;
import android.os.Bundle;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.OnPropertiesChangedListener;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.util.Objects;
import java.util.concurrent.Executor;
/**
* It contains all the keys for the flags, as well as caches some of latest flag values from
* DeviceConfig.
*
* <p>Though the latest flag values can always be retrieved by calling {@code
* DeviceConfig.getProperty}, we want to cache some of those values. For example, the sampling
* intervals for logging, they are needed for each api call and it would be a little expensive to
* call
* {@code DeviceConfig.getProperty} every time.
*
* <p>Listener is registered to DeviceConfig keep the cached value up to date.
*
* <p>This class is thread-safe.
*
* @hide
*/
public final class AppSearchConfig implements AutoCloseable {
private static volatile AppSearchConfig sConfig;
/**
* It would be used as default min time interval between samples in millis if there is no value
* set for {@link AppSearchConfig#KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS} in DeviceConfig.
*/
@VisibleForTesting
static final long DEFAULT_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS = 50;
/**
* It would be used as default sampling interval if there is no value
* set for {@link AppSearchConfig#KEY_SAMPLING_INTERVAL_DEFAULT} in DeviceConfig.
*/
@VisibleForTesting
static final int DEFAULT_SAMPLING_INTERVAL = 10;
@VisibleForTesting
static final int DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES = 512 * 1024; // 512KiB
@VisibleForTesting
static final int DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_COUNT = 20_000;
@VisibleForTesting
static final int DEFAULT_BYTES_OPTIMIZE_THRESHOLD = 1 * 1024 * 1024; // 1 MiB
@VisibleForTesting
static final int DEFAULT_TIME_OPTIMIZE_THRESHOLD_MILLIS = Integer.MAX_VALUE;
@VisibleForTesting
static final int DEFAULT_DOC_COUNT_OPTIMIZE_THRESHOLD = 10_000;
/*
* Keys for ALL the flags stored in DeviceConfig.
*/
public static final String KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS =
"min_time_interval_between_samples_millis";
public static final String KEY_SAMPLING_INTERVAL_DEFAULT = "sampling_interval_default";
public static final String KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS =
"sampling_interval_for_batch_call_stats";
public static final String KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS =
"sampling_interval_for_put_document_stats";
public static final String KEY_SAMPLING_INTERVAL_FOR_INITIALIZE_STATS =
"sampling_interval_for_initialize_stats";
public static final String KEY_SAMPLING_INTERVAL_FOR_SEARCH_STATS =
"sampling_interval_for_search_stats";
public static final String KEY_SAMPLING_INTERVAL_FOR_GLOBAL_SEARCH_STATS =
"sampling_interval_for_global_search_stats";
public static final String KEY_SAMPLING_INTERVAL_FOR_OPTIMIZE_STATS =
"sampling_interval_for_optimize_stats";
public static final String KEY_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES =
"limit_config_max_document_size_bytes";
public static final String KEY_LIMIT_CONFIG_MAX_DOCUMENT_COUNT =
"limit_config_max_document_docunt";
public static final String KEY_BYTES_OPTIMIZE_THRESHOLD = "bytes_optimize_threshold";
public static final String KEY_TIME_OPTIMIZE_THRESHOLD_MILLIS = "time_optimize_threshold";
public static final String KEY_DOC_COUNT_OPTIMIZE_THRESHOLD = "doc_count_optimize_threshold";
// Array contains all the corresponding keys for the cached values.
private static final String[] KEYS_TO_ALL_CACHED_VALUES = {
KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
KEY_SAMPLING_INTERVAL_DEFAULT,
KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS,
KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS,
KEY_SAMPLING_INTERVAL_FOR_INITIALIZE_STATS,
KEY_SAMPLING_INTERVAL_FOR_SEARCH_STATS,
KEY_SAMPLING_INTERVAL_FOR_GLOBAL_SEARCH_STATS,
KEY_SAMPLING_INTERVAL_FOR_OPTIMIZE_STATS,
KEY_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES,
KEY_LIMIT_CONFIG_MAX_DOCUMENT_COUNT,
KEY_BYTES_OPTIMIZE_THRESHOLD,
KEY_TIME_OPTIMIZE_THRESHOLD_MILLIS,
KEY_DOC_COUNT_OPTIMIZE_THRESHOLD
};
// Lock needed for all the operations in this class.
private final Object mLock = new Object();
/**
* Bundle to hold all the cached flag values corresponding to
* {@link AppSearchConfig#KEYS_TO_ALL_CACHED_VALUES}.
*/
@GuardedBy("mLock")
private final Bundle mBundleLocked = new Bundle();
@GuardedBy("mLock")
private boolean mIsClosedLocked = false;
/** Listener to update cached flag values from DeviceConfig. */
private final OnPropertiesChangedListener mOnDeviceConfigChangedListener =
properties -> {
if (!properties.getNamespace().equals(DeviceConfig.NAMESPACE_APPSEARCH)) {
return;
}
updateCachedValues(properties);
};
private AppSearchConfig() {
}
/**
* Creates an instance of {@link AppSearchConfig}.
*
* @param executor used to fetch and cache the flag values from DeviceConfig during creation or
* config change.
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@NonNull
public static AppSearchConfig create(@NonNull Executor executor) {
Objects.requireNonNull(executor);
AppSearchConfig configManager = new AppSearchConfig();
configManager.initialize(executor);
return configManager;
}
/**
* Gets an instance of {@link AppSearchConfig} 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 AppSearchConfig getInstance(@NonNull Executor executor) {
Objects.requireNonNull(executor);
if (sConfig == null) {
synchronized (AppSearchConfig.class) {
if (sConfig == null) {
sConfig = create(executor);
}
}
}
return sConfig;
}
/**
* Initializes the {@link AppSearchConfig}
*
* <p>It fetches the custom properties from DeviceConfig if available.
*
* @param executor listener would be run on to handle P/H flag change.
*/
private void initialize(@NonNull Executor executor) {
executor.execute(() -> {
// Attach the callback to get updates on those properties.
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_APPSEARCH,
executor,
mOnDeviceConfigChangedListener);
DeviceConfig.Properties properties = DeviceConfig.getProperties(
DeviceConfig.NAMESPACE_APPSEARCH, KEYS_TO_ALL_CACHED_VALUES);
updateCachedValues(properties);
});
}
// TODO(b/173532925) check this will be called. If we have a singleton instance for this
// class, probably we don't need it.
@Override
public void close() {
synchronized (mLock) {
if (mIsClosedLocked) {
return;
}
DeviceConfig.removeOnPropertiesChangedListener(mOnDeviceConfigChangedListener);
mIsClosedLocked = true;
}
}
/** Returns cached value for minTimeIntervalBetweenSamplesMillis. */
public long getCachedMinTimeIntervalBetweenSamplesMillis() {
synchronized (mLock) {
throwIfClosedLocked();
return mBundleLocked.getLong(KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
DEFAULT_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS);
}
}
/**
* Returns cached value for default sampling interval for all the stats NOT listed in
* the configuration.
*
* <p>For example, sampling_interval=10 means that one out of every 10 stats was logged.
*/
public int getCachedSamplingIntervalDefault() {
synchronized (mLock) {
throwIfClosedLocked();
return mBundleLocked.getInt(KEY_SAMPLING_INTERVAL_DEFAULT, DEFAULT_SAMPLING_INTERVAL);
}
}
/**
* Returns cached value for sampling interval for batch calls.
*
* <p>For example, sampling_interval=10 means that one out of every 10 stats was logged.
*/
public int getCachedSamplingIntervalForBatchCallStats() {
synchronized (mLock) {
throwIfClosedLocked();
return mBundleLocked.getInt(KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS,
getCachedSamplingIntervalDefault());
}
}
/**
* Returns cached value for sampling interval for putDocument.
*
* <p>For example, sampling_interval=10 means that one out of every 10 stats was logged.
*/
public int getCachedSamplingIntervalForPutDocumentStats() {
synchronized (mLock) {
throwIfClosedLocked();
return mBundleLocked.getInt(KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS,
getCachedSamplingIntervalDefault());
}
}
/**
* Returns cached value for sampling interval for initialize.
*
* <p>For example, sampling_interval=10 means that one out of every 10 stats was logged.
*/
public int getCachedSamplingIntervalForInitializeStats() {
synchronized (mLock) {
throwIfClosedLocked();
return mBundleLocked.getInt(KEY_SAMPLING_INTERVAL_FOR_INITIALIZE_STATS,
getCachedSamplingIntervalDefault());
}
}
/**
* Returns cached value for sampling interval for search.
*
* <p>For example, sampling_interval=10 means that one out of every 10 stats was logged.
*/
public int getCachedSamplingIntervalForSearchStats() {
synchronized (mLock) {
throwIfClosedLocked();
return mBundleLocked.getInt(KEY_SAMPLING_INTERVAL_FOR_SEARCH_STATS,
getCachedSamplingIntervalDefault());
}
}
/**
* Returns cached value for sampling interval for globalSearch.
*
* <p>For example, sampling_interval=10 means that one out of every 10 stats was logged.
*/
public int getCachedSamplingIntervalForGlobalSearchStats() {
synchronized (mLock) {
throwIfClosedLocked();
return mBundleLocked.getInt(KEY_SAMPLING_INTERVAL_FOR_GLOBAL_SEARCH_STATS,
getCachedSamplingIntervalDefault());
}
}
/**
* Returns cached value for sampling interval for optimize.
*
* <p>For example, sampling_interval=10 means that one out of every 10 stats was logged.
*/
public int getCachedSamplingIntervalForOptimizeStats() {
synchronized (mLock) {
throwIfClosedLocked();
return mBundleLocked.getInt(KEY_SAMPLING_INTERVAL_FOR_OPTIMIZE_STATS,
getCachedSamplingIntervalDefault());
}
}
/** Returns the maximum serialized size an indexed document can be, in bytes. */
public int getCachedLimitConfigMaxDocumentSizeBytes() {
synchronized (mLock) {
throwIfClosedLocked();
return mBundleLocked.getInt(KEY_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES,
DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES);
}
}
/** Returns the maximum number of active docs allowed per package. */
public int getCachedLimitConfigMaxDocumentCount() {
synchronized (mLock) {
throwIfClosedLocked();
return mBundleLocked.getInt(KEY_LIMIT_CONFIG_MAX_DOCUMENT_COUNT,
DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_COUNT);
}
}
/**
* Returns the cached optimize byte size threshold.
*
* An AppSearch Optimize job will be triggered if the bytes size of garbage resource exceeds
* this threshold.
*/
int getCachedBytesOptimizeThreshold() {
synchronized (mLock) {
throwIfClosedLocked();
return mBundleLocked.getInt(KEY_BYTES_OPTIMIZE_THRESHOLD,
DEFAULT_BYTES_OPTIMIZE_THRESHOLD);
}
}
/**
* Returns the cached optimize time interval threshold.
*
* An AppSearch Optimize job will be triggered if the time since last optimize job exceeds
* this threshold.
*/
int getCachedTimeOptimizeThresholdMs() {
synchronized (mLock) {
throwIfClosedLocked();
return mBundleLocked.getInt(KEY_TIME_OPTIMIZE_THRESHOLD_MILLIS,
DEFAULT_TIME_OPTIMIZE_THRESHOLD_MILLIS);
}
}
/**
* Returns the cached optimize document count threshold threshold.
*
* An AppSearch Optimize job will be triggered if the number of document of garbage resource
* exceeds this threshold.
*/
int getCachedDocCountOptimizeThreshold() {
synchronized (mLock) {
throwIfClosedLocked();
return mBundleLocked.getInt(KEY_DOC_COUNT_OPTIMIZE_THRESHOLD,
DEFAULT_DOC_COUNT_OPTIMIZE_THRESHOLD);
}
}
@GuardedBy("mLock")
private void throwIfClosedLocked() {
if (mIsClosedLocked) {
throw new IllegalStateException("Trying to use a closed AppSearchConfig instance.");
}
}
private void updateCachedValues(@NonNull DeviceConfig.Properties properties) {
for (String key : properties.getKeyset()) {
updateCachedValue(key, properties);
}
}
private void updateCachedValue(@NonNull String key,
@NonNull DeviceConfig.Properties properties) {
if (properties.getString(key, /*defaultValue=*/ null) == null) {
// Key is missing or value is just null. That is not expected if the key is
// defined in the configuration.
//
// We choose NOT to put the default value in the bundle.
// Instead, we let the getters handle what default value should be returned.
//
// Also we keep the old value in the bundle. So getters can still
// return last valid value.
return;
}
switch (key) {
case KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS:
synchronized (mLock) {
mBundleLocked.putLong(key,
properties.getLong(key,
DEFAULT_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS));
}
break;
case KEY_SAMPLING_INTERVAL_DEFAULT:
case KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS:
case KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS:
case KEY_SAMPLING_INTERVAL_FOR_INITIALIZE_STATS:
case KEY_SAMPLING_INTERVAL_FOR_SEARCH_STATS:
case KEY_SAMPLING_INTERVAL_FOR_GLOBAL_SEARCH_STATS:
case KEY_SAMPLING_INTERVAL_FOR_OPTIMIZE_STATS:
synchronized (mLock) {
mBundleLocked.putInt(key, properties.getInt(key, DEFAULT_SAMPLING_INTERVAL));
}
break;
case KEY_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES:
synchronized (mLock) {
mBundleLocked.putInt(
key,
properties.getInt(key, DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES));
}
break;
case KEY_LIMIT_CONFIG_MAX_DOCUMENT_COUNT:
synchronized (mLock) {
mBundleLocked.putInt(
key,
properties.getInt(key, DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_COUNT));
}
break;
case KEY_BYTES_OPTIMIZE_THRESHOLD:
synchronized (mLock) {
mBundleLocked.putInt(key, properties.getInt(key,
DEFAULT_BYTES_OPTIMIZE_THRESHOLD));
}
break;
case KEY_TIME_OPTIMIZE_THRESHOLD_MILLIS:
synchronized (mLock) {
mBundleLocked.putInt(key, properties.getInt(key,
DEFAULT_TIME_OPTIMIZE_THRESHOLD_MILLIS));
}
break;
case KEY_DOC_COUNT_OPTIMIZE_THRESHOLD:
synchronized (mLock) {
mBundleLocked.putInt(key, properties.getInt(key,
DEFAULT_DOC_COUNT_OPTIMIZE_THRESHOLD));
}
break;
default:
break;
}
}
}

View File

@@ -1,58 +0,0 @@
/*
* 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;
import android.annotation.NonNull;
import com.android.server.appsearch.external.localstorage.AppSearchImpl;
import com.android.server.appsearch.stats.PlatformLogger;
import com.android.server.appsearch.visibilitystore.VisibilityStoreImpl;
import java.util.Objects;
/**
* Container for AppSearch classes that should only be initialized once per device-user and make up
* the core of the AppSearch system.
*/
public final class AppSearchUserInstance {
private final PlatformLogger mLogger;
private final AppSearchImpl mAppSearchImpl;
private final VisibilityStoreImpl mVisibilityStore;
AppSearchUserInstance(
@NonNull PlatformLogger logger,
@NonNull AppSearchImpl appSearchImpl,
@NonNull VisibilityStoreImpl visibilityStore) {
mLogger = Objects.requireNonNull(logger);
mAppSearchImpl = Objects.requireNonNull(appSearchImpl);
mVisibilityStore = Objects.requireNonNull(visibilityStore);
}
@NonNull
public PlatformLogger getLogger() {
return mLogger;
}
@NonNull
public AppSearchImpl getAppSearchImpl() {
return mAppSearchImpl;
}
@NonNull
public VisibilityStoreImpl getVisibilityStore() {
return mVisibilityStore;
}
}

View File

@@ -1,211 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.appsearch;
import android.annotation.NonNull;
import android.app.appsearch.exceptions.AppSearchException;
import android.content.Context;
import android.os.Environment;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.server.appsearch.external.localstorage.AppSearchImpl;
import com.android.server.appsearch.external.localstorage.stats.InitializeStats;
import com.android.server.appsearch.stats.PlatformLogger;
import com.android.server.appsearch.visibilitystore.VisibilityStoreImpl;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* Manages the lifecycle of AppSearch classes that should only be initialized once per device-user
* and make up the core of the AppSearch system.
*
* @hide
*/
public final class AppSearchUserInstanceManager {
private static final String TAG = "AppSearchUserInstanceMa";
private static volatile AppSearchUserInstanceManager sAppSearchUserInstanceManager;
@GuardedBy("mInstancesLocked")
private final Map<UserHandle, AppSearchUserInstance> mInstancesLocked = new ArrayMap<>();
private AppSearchUserInstanceManager() {}
/**
* Gets an instance of AppSearchUserInstanceManager 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 AppSearchUserInstanceManager getInstance() {
if (sAppSearchUserInstanceManager == null) {
synchronized (AppSearchUserInstanceManager.class) {
if (sAppSearchUserInstanceManager == null) {
sAppSearchUserInstanceManager = new AppSearchUserInstanceManager();
}
}
}
return sAppSearchUserInstanceManager;
}
/**
* Returns AppSearch directory in the credential encrypted system directory for the given user.
*
* <p>This folder should only be accessed after unlock.
*/
public static File getAppSearchDir(@NonNull UserHandle userHandle) {
// Duplicates the implementation of Environment#getDataSystemCeDirectory
// TODO(b/191059409): Unhide Environment#getDataSystemCeDirectory and switch to it.
File systemCeDir = new File(Environment.getDataDirectory(), "system_ce");
File systemCeUserDir = new File(systemCeDir, String.valueOf(userHandle.getIdentifier()));
return new File(systemCeUserDir, "appsearch");
}
/**
* Gets an instance of AppSearchUserInstance for the given user, or creates one if none exists.
*
* <p>If no AppSearchUserInstance exists for the unlocked user, Icing will be initialized and
* one will be created.
*
* @param userContext Context of the user calling AppSearch
* @param userHandle The multi-user handle of the device user calling AppSearch
* @param config Flag manager for AppSearch
* @return An initialized {@link AppSearchUserInstance} for this user
*/
@NonNull
public AppSearchUserInstance getOrCreateUserInstance(
@NonNull Context userContext,
@NonNull UserHandle userHandle,
@NonNull AppSearchConfig config)
throws AppSearchException {
Objects.requireNonNull(userContext);
Objects.requireNonNull(userHandle);
Objects.requireNonNull(config);
synchronized (mInstancesLocked) {
AppSearchUserInstance instance = mInstancesLocked.get(userHandle);
if (instance == null) {
instance = createUserInstance(userContext, userHandle, config);
mInstancesLocked.put(userHandle, instance);
}
return instance;
}
}
/**
* Closes and removes an {@link AppSearchUserInstance} for the given user.
*
* <p>All mutations applied to the underlying {@link AppSearchImpl} will be persisted to disk.
*
* @param userHandle The multi-user user handle of the user that need to be removed.
*/
public void closeAndRemoveUserInstance(@NonNull UserHandle userHandle) {
Objects.requireNonNull(userHandle);
synchronized (mInstancesLocked) {
AppSearchUserInstance instance = mInstancesLocked.remove(userHandle);
if (instance != null) {
instance.getAppSearchImpl().close();
}
}
}
/**
* Gets an {@link AppSearchUserInstance} for the given user.
*
* <p>This method should only be called by an initialized SearchSession, which has already
* called {@link #getOrCreateUserInstance} before.
*
* @param userHandle The multi-user handle of the device user calling AppSearch
* @return An initialized {@link AppSearchUserInstance} for this user
* @throws IllegalStateException if {@link AppSearchUserInstance} haven't created for the given
* user.
*/
@NonNull
public AppSearchUserInstance getUserInstance(@NonNull UserHandle userHandle) {
Objects.requireNonNull(userHandle);
synchronized (mInstancesLocked) {
AppSearchUserInstance instance = mInstancesLocked.get(userHandle);
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(
"AppSearchUserInstance has never been created for: " + userHandle);
}
return instance;
}
}
/**
* Returns the list of all {@link UserHandle}s.
*
* <p>It can return an empty list if there is no {@link AppSearchUserInstance} created yet.
*/
@NonNull
public List<UserHandle> getAllUserHandles() {
synchronized (mInstancesLocked) {
return new ArrayList<>(mInstancesLocked.keySet());
}
}
@NonNull
private AppSearchUserInstance createUserInstance(
@NonNull Context userContext,
@NonNull UserHandle userHandle,
@NonNull AppSearchConfig config)
throws AppSearchException {
long totalLatencyStartMillis = SystemClock.elapsedRealtime();
InitializeStats.Builder initStatsBuilder = new InitializeStats.Builder();
// Initialize the classes that make up AppSearchUserInstance
PlatformLogger logger = new PlatformLogger(userContext, config);
File appSearchDir = getAppSearchDir(userHandle);
File icingDir = new File(appSearchDir, "icing");
Log.i(TAG, "Creating new AppSearch instance at: " + icingDir);
AppSearchImpl appSearchImpl = AppSearchImpl.create(
icingDir,
new FrameworkLimitConfig(config),
initStatsBuilder,
new FrameworkOptimizeStrategy(config));
long prepareVisibilityStoreLatencyStartMillis = SystemClock.elapsedRealtime();
VisibilityStoreImpl visibilityStore =
VisibilityStoreImpl.create(appSearchImpl, userContext);
long prepareVisibilityStoreLatencyEndMillis = SystemClock.elapsedRealtime();
initStatsBuilder
.setTotalLatencyMillis(
(int) (SystemClock.elapsedRealtime() - totalLatencyStartMillis))
.setPrepareVisibilityStoreLatencyMillis(
(int)
(prepareVisibilityStoreLatencyEndMillis
- prepareVisibilityStoreLatencyStartMillis));
logger.logStats(initStatsBuilder.build());
return new AppSearchUserInstance(logger, appSearchImpl, visibilityStore);
}
}

View File

@@ -1,41 +0,0 @@
/*
* 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;
import android.annotation.NonNull;
import com.android.server.appsearch.external.localstorage.LimitConfig;
import java.util.Objects;
class FrameworkLimitConfig implements LimitConfig {
private final AppSearchConfig mAppSearchConfig;
FrameworkLimitConfig(@NonNull AppSearchConfig appSearchConfig) {
mAppSearchConfig = Objects.requireNonNull(appSearchConfig);
}
@Override
public int getMaxDocumentSizeBytes() {
return mAppSearchConfig.getCachedLimitConfigMaxDocumentSizeBytes();
}
@Override
public int getMaxDocumentCount() {
return mAppSearchConfig.getCachedLimitConfigMaxDocumentCount();
}
}

View File

@@ -1,48 +0,0 @@
/*
* 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;
import android.annotation.NonNull;
import com.android.server.appsearch.external.localstorage.AppSearchImpl;
import com.android.server.appsearch.external.localstorage.OptimizeStrategy;
import com.google.android.icing.proto.GetOptimizeInfoResultProto;
import java.util.Objects;
/**
* An implementation of {@link OptimizeStrategy} will determine when to trigger {@link
* AppSearchImpl#optimize()} in Jetpack environment.
*
* @hide
*/
public class FrameworkOptimizeStrategy implements OptimizeStrategy {
private final AppSearchConfig mAppSearchConfig;
FrameworkOptimizeStrategy(@NonNull AppSearchConfig config) {
mAppSearchConfig = Objects.requireNonNull(config);
}
@Override
public boolean shouldOptimize(@NonNull GetOptimizeInfoResultProto optimizeInfo) {
return optimizeInfo.getOptimizableDocs()
>= mAppSearchConfig.getCachedDocCountOptimizeThreshold()
|| optimizeInfo.getEstimatedOptimizableBytes()
>= mAppSearchConfig.getCachedBytesOptimizeThreshold()
|| optimizeInfo.getTimeSinceLastOptimizeMs()
>= mAppSearchConfig.getCachedTimeOptimizeThresholdMs();
}
}

View File

@@ -1,26 +0,0 @@
{
"presubmit": [
{
"name": "CtsAppSearchTestCases"
},
{
"name": "CtsAppSearchHostTestCases"
},
{
"name": "FrameworksServicesTests",
"options": [
{
"include-filter": "com.android.server.appsearch"
}
]
},
{
"name": "FrameworksCoreTests",
"options": [
{
"include-filter": "android.app.appsearch"
}
]
}
]
}

View File

@@ -1,58 +0,0 @@
/*
* Copyright 2020 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.external.localstorage;
import android.annotation.NonNull;
import com.android.server.appsearch.external.localstorage.stats.CallStats;
import com.android.server.appsearch.external.localstorage.stats.InitializeStats;
import com.android.server.appsearch.external.localstorage.stats.OptimizeStats;
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;
/**
* An interface for implementing client-defined logging AppSearch operations stats.
*
* <p>Any implementation needs to provide general information on how to log all the stats types.
* (e.g. {@link CallStats})
*
* <p>All implementations of this interface must be thread safe.
*
* @hide
*/
public interface AppSearchLogger {
/** Logs {@link CallStats} */
void logStats(@NonNull CallStats stats);
/** Logs {@link PutDocumentStats} */
void logStats(@NonNull PutDocumentStats stats);
/** Logs {@link InitializeStats} */
void logStats(@NonNull InitializeStats stats);
/** Logs {@link SearchStats} */
void logStats(@NonNull SearchStats stats);
/** Logs {@link RemoveStats} */
void logStats(@NonNull RemoveStats stats);
/** Logs {@link OptimizeStats} */
void logStats(@NonNull OptimizeStats stats);
// TODO(b/173532925) Add remaining logStats once we add all the stats.
}

View File

@@ -1,167 +0,0 @@
/*
* Copyright 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.external.localstorage;
import android.annotation.NonNull;
import com.android.server.appsearch.external.localstorage.stats.InitializeStats;
import com.android.server.appsearch.external.localstorage.stats.OptimizeStats;
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.google.android.icing.proto.DeleteStatsProto;
import com.google.android.icing.proto.InitializeStatsProto;
import com.google.android.icing.proto.OptimizeStatsProto;
import com.google.android.icing.proto.PutDocumentStatsProto;
import com.google.android.icing.proto.QueryStatsProto;
import java.util.Objects;
/**
* Class contains helper functions for logging.
*
* <p>E.g. we need to have helper functions to copy numbers from IcingLib to stats classes.
*
* @hide
*/
public final class AppSearchLoggerHelper {
private AppSearchLoggerHelper() {}
/**
* Copies native PutDocument stats to builder.
*
* @param fromNativeStats stats copied from
* @param toStatsBuilder stats copied to
*/
static void copyNativeStats(
@NonNull PutDocumentStatsProto fromNativeStats,
@NonNull PutDocumentStats.Builder toStatsBuilder) {
Objects.requireNonNull(fromNativeStats);
Objects.requireNonNull(toStatsBuilder);
toStatsBuilder
.setNativeLatencyMillis(fromNativeStats.getLatencyMs())
.setNativeDocumentStoreLatencyMillis(fromNativeStats.getDocumentStoreLatencyMs())
.setNativeIndexLatencyMillis(fromNativeStats.getIndexLatencyMs())
.setNativeIndexMergeLatencyMillis(fromNativeStats.getIndexMergeLatencyMs())
.setNativeDocumentSizeBytes(fromNativeStats.getDocumentSize())
.setNativeNumTokensIndexed(
fromNativeStats.getTokenizationStats().getNumTokensIndexed())
.setNativeExceededMaxNumTokens(
fromNativeStats.getTokenizationStats().getExceededMaxTokenNum());
}
/**
* Copies native Initialize stats to builder.
*
* @param fromNativeStats stats copied from
* @param toStatsBuilder stats copied to
*/
static void copyNativeStats(
@NonNull InitializeStatsProto fromNativeStats,
@NonNull InitializeStats.Builder toStatsBuilder) {
Objects.requireNonNull(fromNativeStats);
Objects.requireNonNull(toStatsBuilder);
toStatsBuilder
.setNativeLatencyMillis(fromNativeStats.getLatencyMs())
.setDocumentStoreRecoveryCause(
fromNativeStats.getDocumentStoreRecoveryCause().getNumber())
.setIndexRestorationCause(fromNativeStats.getIndexRestorationCause().getNumber())
.setSchemaStoreRecoveryCause(
fromNativeStats.getSchemaStoreRecoveryCause().getNumber())
.setDocumentStoreRecoveryLatencyMillis(
fromNativeStats.getDocumentStoreRecoveryLatencyMs())
.setIndexRestorationLatencyMillis(fromNativeStats.getIndexRestorationLatencyMs())
.setSchemaStoreRecoveryLatencyMillis(
fromNativeStats.getSchemaStoreRecoveryLatencyMs())
.setDocumentStoreDataStatus(
fromNativeStats.getDocumentStoreDataStatus().getNumber())
.setDocumentCount(fromNativeStats.getNumDocuments())
.setSchemaTypeCount(fromNativeStats.getNumSchemaTypes());
}
/**
* Copies native Query stats to builder.
*
* @param fromNativeStats Stats copied from.
* @param toStatsBuilder Stats copied to.
*/
static void copyNativeStats(
@NonNull QueryStatsProto fromNativeStats, @NonNull SearchStats.Builder toStatsBuilder) {
Objects.requireNonNull(fromNativeStats);
Objects.requireNonNull(toStatsBuilder);
toStatsBuilder
.setNativeLatencyMillis(fromNativeStats.getLatencyMs())
.setTermCount(fromNativeStats.getNumTerms())
.setQueryLength(fromNativeStats.getQueryLength())
.setFilteredNamespaceCount(fromNativeStats.getNumNamespacesFiltered())
.setFilteredSchemaTypeCount(fromNativeStats.getNumSchemaTypesFiltered())
.setRequestedPageSize(fromNativeStats.getRequestedPageSize())
.setCurrentPageReturnedResultCount(
fromNativeStats.getNumResultsReturnedCurrentPage())
.setIsFirstPage(fromNativeStats.getIsFirstPage())
.setParseQueryLatencyMillis(fromNativeStats.getParseQueryLatencyMs())
.setRankingStrategy(fromNativeStats.getRankingStrategy().getNumber())
.setScoredDocumentCount(fromNativeStats.getNumDocumentsScored())
.setScoringLatencyMillis(fromNativeStats.getScoringLatencyMs())
.setRankingLatencyMillis(fromNativeStats.getRankingLatencyMs())
.setResultWithSnippetsCount(fromNativeStats.getNumResultsWithSnippets())
.setDocumentRetrievingLatencyMillis(
fromNativeStats.getDocumentRetrievalLatencyMs());
}
/**
* Copies native Delete stats to builder.
*
* @param fromNativeStats Stats copied from.
* @param toStatsBuilder Stats copied to.
*/
static void copyNativeStats(
@NonNull DeleteStatsProto fromNativeStats,
@NonNull RemoveStats.Builder toStatsBuilder) {
Objects.requireNonNull(fromNativeStats);
Objects.requireNonNull(toStatsBuilder);
toStatsBuilder
.setNativeLatencyMillis(fromNativeStats.getLatencyMs())
.setDeleteType(fromNativeStats.getDeleteType().getNumber())
.setDeletedDocumentCount(fromNativeStats.getNumDocumentsDeleted());
}
/**
* Copies native {@link OptimizeStatsProto} to builder.
*
* @param fromNativeStats Stats copied from.
* @param toStatsBuilder Stats copied to.
*/
static void copyNativeStats(
@NonNull OptimizeStatsProto fromNativeStats,
@NonNull OptimizeStats.Builder toStatsBuilder) {
Objects.requireNonNull(fromNativeStats);
Objects.requireNonNull(toStatsBuilder);
toStatsBuilder
.setNativeLatencyMillis(fromNativeStats.getLatencyMs())
.setDocumentStoreOptimizeLatencyMillis(
fromNativeStats.getDocumentStoreOptimizeLatencyMs())
.setIndexRestorationLatencyMillis(fromNativeStats.getIndexRestorationLatencyMs())
.setOriginalDocumentCount(fromNativeStats.getNumOriginalDocuments())
.setDeletedDocumentCount(fromNativeStats.getNumDeletedDocuments())
.setExpiredDocumentCount(fromNativeStats.getNumExpiredDocuments())
.setStorageSizeBeforeBytes(fromNativeStats.getStorageSizeBefore())
.setStorageSizeAfterBytes(fromNativeStats.getStorageSizeAfter())
.setTimeSinceLastOptimizeMillis(fromNativeStats.getTimeSinceLastOptimizeMs());
}
}

View File

@@ -1,57 +0,0 @@
/*
* Copyright 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.external.localstorage;
/**
* Defines limits placed on users of AppSearch and enforced by {@link AppSearchImpl}.
*
* @hide
*/
public interface LimitConfig {
/**
* The maximum number of bytes a single document is allowed to be.
*
* <p>Enforced at the time of serializing the document into a proto.
*
* <p>This limit has two purposes:
*
* <ol>
* <li>Prevent the system service from using too much memory during indexing or querying by
* capping the size of the data structures it needs to buffer
* <li>Prevent apps from using a very large amount of data by storing exceptionally large
* documents.
* </ol>
*/
int getMaxDocumentSizeBytes();
/**
* The maximum number of documents a single app is allowed to index.
*
* <p>Enforced at indexing time.
*
* <p>This limit has two purposes:
*
* <ol>
* <li>Protect icing lib's docid space from being overwhelmed by a single app. The overall
* docid limit is currently 2^20 (~1 million)
* <li>Prevent apps from using a very large amount of data on the system by storing too many
* documents.
* </ol>
*/
int getMaxDocumentCount();
}

View File

@@ -1,39 +0,0 @@
/*
* Copyright 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.external.localstorage;
import android.annotation.NonNull;
import com.google.android.icing.proto.GetOptimizeInfoResultProto;
/**
* An interface class for implementing a strategy to determine when to trigger {@link
* AppSearchImpl#optimize()}.
*
* @hide
*/
public interface OptimizeStrategy {
/**
* Determines whether {@link AppSearchImpl#optimize()} need to be triggered to release garbage
* resources in AppSearch base on the given information.
*
* @param optimizeInfo The proto object indicates the number of garbage resources in AppSearch.
* @return {@code true} if {@link AppSearchImpl#optimize()} need to be triggered.
*/
boolean shouldOptimize(@NonNull GetOptimizeInfoResultProto optimizeInfo);
}

View File

@@ -1,37 +0,0 @@
/*
* Copyright 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.external.localstorage;
/**
* In Jetpack, AppSearch doesn't enforce artificial limits on number of documents or size of
* documents, since the app is the only user of the Icing instance. Icing still enforces a docid
* limit of 1M docs.
*
* @hide
*/
public class UnlimitedLimitConfig implements LimitConfig {
@Override
public int getMaxDocumentSizeBytes() {
return Integer.MAX_VALUE;
}
@Override
public int getMaxDocumentCount() {
return Integer.MAX_VALUE;
}
}

View File

@@ -1,221 +0,0 @@
/*
* Copyright 2020 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.external.localstorage.converter;
import android.annotation.NonNull;
import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.GenericDocument;
import com.google.android.icing.proto.DocumentProto;
import com.google.android.icing.proto.PropertyProto;
import com.google.android.icing.proto.SchemaTypeConfigProto;
import com.google.protobuf.ByteString;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
/**
* Translates a {@link GenericDocument} into a {@link DocumentProto}.
*
* @hide
*/
public final class GenericDocumentToProtoConverter {
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private static final long[] EMPTY_LONG_ARRAY = new long[0];
private static final double[] EMPTY_DOUBLE_ARRAY = new double[0];
private static final boolean[] EMPTY_BOOLEAN_ARRAY = new boolean[0];
private static final byte[][] EMPTY_BYTES_ARRAY = new byte[0][0];
private static final GenericDocument[] EMPTY_DOCUMENT_ARRAY = new GenericDocument[0];
private GenericDocumentToProtoConverter() {}
/** Converts a {@link GenericDocument} into a {@link DocumentProto}. */
@NonNull
@SuppressWarnings("unchecked")
public static DocumentProto toDocumentProto(@NonNull GenericDocument document) {
Objects.requireNonNull(document);
DocumentProto.Builder mProtoBuilder = DocumentProto.newBuilder();
mProtoBuilder
.setUri(document.getId())
.setSchema(document.getSchemaType())
.setNamespace(document.getNamespace())
.setScore(document.getScore())
.setTtlMs(document.getTtlMillis())
.setCreationTimestampMs(document.getCreationTimestampMillis());
ArrayList<String> keys = new ArrayList<>(document.getPropertyNames());
Collections.sort(keys);
for (int i = 0; i < keys.size(); i++) {
String name = keys.get(i);
PropertyProto.Builder propertyProto = PropertyProto.newBuilder().setName(name);
Object property = document.getProperty(name);
if (property instanceof String[]) {
String[] stringValues = (String[]) property;
for (int j = 0; j < stringValues.length; j++) {
propertyProto.addStringValues(stringValues[j]);
}
} else if (property instanceof long[]) {
long[] longValues = (long[]) property;
for (int j = 0; j < longValues.length; j++) {
propertyProto.addInt64Values(longValues[j]);
}
} else if (property instanceof double[]) {
double[] doubleValues = (double[]) property;
for (int j = 0; j < doubleValues.length; j++) {
propertyProto.addDoubleValues(doubleValues[j]);
}
} else if (property instanceof boolean[]) {
boolean[] booleanValues = (boolean[]) property;
for (int j = 0; j < booleanValues.length; j++) {
propertyProto.addBooleanValues(booleanValues[j]);
}
} else if (property instanceof byte[][]) {
byte[][] bytesValues = (byte[][]) property;
for (int j = 0; j < bytesValues.length; j++) {
propertyProto.addBytesValues(ByteString.copyFrom(bytesValues[j]));
}
} else if (property instanceof GenericDocument[]) {
GenericDocument[] documentValues = (GenericDocument[]) property;
for (int j = 0; j < documentValues.length; j++) {
DocumentProto proto = toDocumentProto(documentValues[j]);
propertyProto.addDocumentValues(proto);
}
} else {
throw new IllegalStateException(
String.format(
"Property \"%s\" has unsupported value type %s",
name, property.getClass().toString()));
}
mProtoBuilder.addProperties(propertyProto);
}
return mProtoBuilder.build();
}
/**
* Converts a {@link DocumentProto} into a {@link GenericDocument}.
*
* <p>In the case that the {@link DocumentProto} object proto has no values set, the converter
* searches for the matching property name in the {@link SchemaTypeConfigProto} object for the
* document, and infers the correct default value to set for the empty property based on the
* data type of the property defined by the schema type.
*
* @param proto the document to convert to a {@link GenericDocument} instance. The document
* proto should have its package + database prefix stripped from its fields.
* @param prefix the package + database prefix used searching the {@code schemaTypeMap}.
* @param schemaTypeMap map of prefixed schema type to {@link SchemaTypeConfigProto}, used for
* looking up the default empty value to set for a document property that has all empty
* values.
*/
@NonNull
public static GenericDocument toGenericDocument(
@NonNull DocumentProto proto,
@NonNull String prefix,
@NonNull Map<String, SchemaTypeConfigProto> schemaTypeMap) {
Objects.requireNonNull(proto);
GenericDocument.Builder<?> documentBuilder =
new GenericDocument.Builder<>(
proto.getNamespace(), proto.getUri(), proto.getSchema())
.setScore(proto.getScore())
.setTtlMillis(proto.getTtlMs())
.setCreationTimestampMillis(proto.getCreationTimestampMs());
String prefixedSchemaType = prefix + proto.getSchema();
for (int i = 0; i < proto.getPropertiesCount(); i++) {
PropertyProto property = proto.getProperties(i);
String name = property.getName();
if (property.getStringValuesCount() > 0) {
String[] values = new String[property.getStringValuesCount()];
for (int j = 0; j < values.length; j++) {
values[j] = property.getStringValues(j);
}
documentBuilder.setPropertyString(name, values);
} else if (property.getInt64ValuesCount() > 0) {
long[] values = new long[property.getInt64ValuesCount()];
for (int j = 0; j < values.length; j++) {
values[j] = property.getInt64Values(j);
}
documentBuilder.setPropertyLong(name, values);
} else if (property.getDoubleValuesCount() > 0) {
double[] values = new double[property.getDoubleValuesCount()];
for (int j = 0; j < values.length; j++) {
values[j] = property.getDoubleValues(j);
}
documentBuilder.setPropertyDouble(name, values);
} else if (property.getBooleanValuesCount() > 0) {
boolean[] values = new boolean[property.getBooleanValuesCount()];
for (int j = 0; j < values.length; j++) {
values[j] = property.getBooleanValues(j);
}
documentBuilder.setPropertyBoolean(name, values);
} else if (property.getBytesValuesCount() > 0) {
byte[][] values = new byte[property.getBytesValuesCount()][];
for (int j = 0; j < values.length; j++) {
values[j] = property.getBytesValues(j).toByteArray();
}
documentBuilder.setPropertyBytes(name, values);
} else if (property.getDocumentValuesCount() > 0) {
GenericDocument[] values = new GenericDocument[property.getDocumentValuesCount()];
for (int j = 0; j < values.length; j++) {
values[j] =
toGenericDocument(property.getDocumentValues(j), prefix, schemaTypeMap);
}
documentBuilder.setPropertyDocument(name, values);
} else {
// TODO(b/184966497): Optimize by caching PropertyConfigProto
setEmptyProperty(name, documentBuilder, schemaTypeMap.get(prefixedSchemaType));
}
}
return documentBuilder.build();
}
private static void setEmptyProperty(
@NonNull String propertyName,
@NonNull GenericDocument.Builder<?> documentBuilder,
@NonNull SchemaTypeConfigProto schema) {
@AppSearchSchema.PropertyConfig.DataType int dataType = 0;
for (int i = 0; i < schema.getPropertiesCount(); ++i) {
if (propertyName.equals(schema.getProperties(i).getPropertyName())) {
dataType = schema.getProperties(i).getDataType().getNumber();
break;
}
}
switch (dataType) {
case AppSearchSchema.PropertyConfig.DATA_TYPE_STRING:
documentBuilder.setPropertyString(propertyName, EMPTY_STRING_ARRAY);
break;
case AppSearchSchema.PropertyConfig.DATA_TYPE_LONG:
documentBuilder.setPropertyLong(propertyName, EMPTY_LONG_ARRAY);
break;
case AppSearchSchema.PropertyConfig.DATA_TYPE_DOUBLE:
documentBuilder.setPropertyDouble(propertyName, EMPTY_DOUBLE_ARRAY);
break;
case AppSearchSchema.PropertyConfig.DATA_TYPE_BOOLEAN:
documentBuilder.setPropertyBoolean(propertyName, EMPTY_BOOLEAN_ARRAY);
break;
case AppSearchSchema.PropertyConfig.DATA_TYPE_BYTES:
documentBuilder.setPropertyBytes(propertyName, EMPTY_BYTES_ARRAY);
break;
case AppSearchSchema.PropertyConfig.DATA_TYPE_DOCUMENT:
documentBuilder.setPropertyDocument(propertyName, EMPTY_DOCUMENT_ARRAY);
break;
default:
throw new IllegalStateException("Unknown type of value: " + propertyName);
}
}
}

View File

@@ -1,62 +0,0 @@
/*
* Copyright 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.external.localstorage.converter;
import android.annotation.NonNull;
import android.app.appsearch.AppSearchResult;
import android.util.Log;
import com.google.android.icing.proto.StatusProto;
/**
* Translates an {@link StatusProto.Code} into a {@link AppSearchResult.ResultCode}
*
* @hide
*/
public final class ResultCodeToProtoConverter {
private static final String TAG = "AppSearchResultCodeToPr";
private ResultCodeToProtoConverter() {}
/** Converts an {@link StatusProto.Code} into a {@link AppSearchResult.ResultCode}. */
public static @AppSearchResult.ResultCode int toResultCode(
@NonNull StatusProto.Code statusCode) {
switch (statusCode) {
case OK:
return AppSearchResult.RESULT_OK;
case OUT_OF_SPACE:
return AppSearchResult.RESULT_OUT_OF_SPACE;
case INTERNAL:
return AppSearchResult.RESULT_INTERNAL_ERROR;
case UNKNOWN:
return AppSearchResult.RESULT_UNKNOWN_ERROR;
case NOT_FOUND:
return AppSearchResult.RESULT_NOT_FOUND;
case INVALID_ARGUMENT:
return AppSearchResult.RESULT_INVALID_ARGUMENT;
default:
// Some unknown/unsupported error
Log.e(
TAG,
"Cannot convert IcingSearchEngine status code: "
+ statusCode
+ " to AppSearchResultCode.");
return AppSearchResult.RESULT_INTERNAL_ERROR;
}
}
}

View File

@@ -1,227 +0,0 @@
/*
* Copyright 2020 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.external.localstorage.converter;
import android.annotation.NonNull;
import android.app.appsearch.AppSearchSchema;
import android.util.Log;
import com.google.android.icing.proto.DocumentIndexingConfig;
import com.google.android.icing.proto.PropertyConfigProto;
import com.google.android.icing.proto.SchemaTypeConfigProto;
import com.google.android.icing.proto.SchemaTypeConfigProtoOrBuilder;
import com.google.android.icing.proto.StringIndexingConfig;
import com.google.android.icing.proto.TermMatchType;
import java.util.List;
import java.util.Objects;
/**
* Translates an {@link AppSearchSchema} into a {@link SchemaTypeConfigProto}.
*
* @hide
*/
public final class SchemaToProtoConverter {
private static final String TAG = "AppSearchSchemaToProtoC";
private SchemaToProtoConverter() {}
/**
* Converts an {@link android.app.appsearch.AppSearchSchema} into a {@link
* SchemaTypeConfigProto}.
*/
@NonNull
public static SchemaTypeConfigProto toSchemaTypeConfigProto(
@NonNull AppSearchSchema schema, int version) {
Objects.requireNonNull(schema);
SchemaTypeConfigProto.Builder protoBuilder =
SchemaTypeConfigProto.newBuilder()
.setSchemaType(schema.getSchemaType())
.setVersion(version);
List<AppSearchSchema.PropertyConfig> properties = schema.getProperties();
for (int i = 0; i < properties.size(); i++) {
PropertyConfigProto propertyProto = toPropertyConfigProto(properties.get(i));
protoBuilder.addProperties(propertyProto);
}
return protoBuilder.build();
}
@NonNull
private static PropertyConfigProto toPropertyConfigProto(
@NonNull AppSearchSchema.PropertyConfig property) {
Objects.requireNonNull(property);
PropertyConfigProto.Builder builder =
PropertyConfigProto.newBuilder().setPropertyName(property.getName());
// Set dataType
@AppSearchSchema.PropertyConfig.DataType int dataType = property.getDataType();
PropertyConfigProto.DataType.Code dataTypeProto =
PropertyConfigProto.DataType.Code.forNumber(dataType);
if (dataTypeProto == null) {
throw new IllegalArgumentException("Invalid dataType: " + dataType);
}
builder.setDataType(dataTypeProto);
// Set cardinality
@AppSearchSchema.PropertyConfig.Cardinality int cardinality = property.getCardinality();
PropertyConfigProto.Cardinality.Code cardinalityProto =
PropertyConfigProto.Cardinality.Code.forNumber(cardinality);
if (cardinalityProto == null) {
throw new IllegalArgumentException("Invalid cardinality: " + dataType);
}
builder.setCardinality(cardinalityProto);
if (property instanceof AppSearchSchema.StringPropertyConfig) {
AppSearchSchema.StringPropertyConfig stringProperty =
(AppSearchSchema.StringPropertyConfig) property;
StringIndexingConfig stringIndexingConfig =
StringIndexingConfig.newBuilder()
.setTermMatchType(
convertTermMatchTypeToProto(stringProperty.getIndexingType()))
.setTokenizerType(
convertTokenizerTypeToProto(stringProperty.getTokenizerType()))
.build();
builder.setStringIndexingConfig(stringIndexingConfig);
} else if (property instanceof AppSearchSchema.DocumentPropertyConfig) {
AppSearchSchema.DocumentPropertyConfig documentProperty =
(AppSearchSchema.DocumentPropertyConfig) property;
builder.setSchemaType(documentProperty.getSchemaType())
.setDocumentIndexingConfig(
DocumentIndexingConfig.newBuilder()
.setIndexNestedProperties(
documentProperty.shouldIndexNestedProperties()));
}
return builder.build();
}
/**
* Converts a {@link SchemaTypeConfigProto} into an {@link
* android.app.appsearch.AppSearchSchema}.
*/
@NonNull
public static AppSearchSchema toAppSearchSchema(@NonNull SchemaTypeConfigProtoOrBuilder proto) {
Objects.requireNonNull(proto);
AppSearchSchema.Builder builder = new AppSearchSchema.Builder(proto.getSchemaType());
List<PropertyConfigProto> properties = proto.getPropertiesList();
for (int i = 0; i < properties.size(); i++) {
AppSearchSchema.PropertyConfig propertyConfig = toPropertyConfig(properties.get(i));
builder.addProperty(propertyConfig);
}
return builder.build();
}
@NonNull
private static AppSearchSchema.PropertyConfig toPropertyConfig(
@NonNull PropertyConfigProto proto) {
Objects.requireNonNull(proto);
switch (proto.getDataType()) {
case STRING:
return toStringPropertyConfig(proto);
case INT64:
return new AppSearchSchema.LongPropertyConfig.Builder(proto.getPropertyName())
.setCardinality(proto.getCardinality().getNumber())
.build();
case DOUBLE:
return new AppSearchSchema.DoublePropertyConfig.Builder(proto.getPropertyName())
.setCardinality(proto.getCardinality().getNumber())
.build();
case BOOLEAN:
return new AppSearchSchema.BooleanPropertyConfig.Builder(proto.getPropertyName())
.setCardinality(proto.getCardinality().getNumber())
.build();
case BYTES:
return new AppSearchSchema.BytesPropertyConfig.Builder(proto.getPropertyName())
.setCardinality(proto.getCardinality().getNumber())
.build();
case DOCUMENT:
return toDocumentPropertyConfig(proto);
default:
throw new IllegalArgumentException("Invalid dataType: " + proto.getDataType());
}
}
@NonNull
private static AppSearchSchema.StringPropertyConfig toStringPropertyConfig(
@NonNull PropertyConfigProto proto) {
AppSearchSchema.StringPropertyConfig.Builder builder =
new AppSearchSchema.StringPropertyConfig.Builder(proto.getPropertyName())
.setCardinality(proto.getCardinality().getNumber())
.setTokenizerType(
proto.getStringIndexingConfig().getTokenizerType().getNumber());
// Set indexingType
TermMatchType.Code termMatchTypeProto = proto.getStringIndexingConfig().getTermMatchType();
builder.setIndexingType(convertTermMatchTypeFromProto(termMatchTypeProto));
return builder.build();
}
@NonNull
private static AppSearchSchema.DocumentPropertyConfig toDocumentPropertyConfig(
@NonNull PropertyConfigProto proto) {
return new AppSearchSchema.DocumentPropertyConfig.Builder(
proto.getPropertyName(), proto.getSchemaType())
.setCardinality(proto.getCardinality().getNumber())
.setShouldIndexNestedProperties(
proto.getDocumentIndexingConfig().getIndexNestedProperties())
.build();
}
@NonNull
private static TermMatchType.Code convertTermMatchTypeToProto(
@AppSearchSchema.StringPropertyConfig.IndexingType int indexingType) {
switch (indexingType) {
case AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE:
return TermMatchType.Code.UNKNOWN;
case AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS:
return TermMatchType.Code.EXACT_ONLY;
case AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES:
return TermMatchType.Code.PREFIX;
default:
throw new IllegalArgumentException("Invalid indexingType: " + indexingType);
}
}
@AppSearchSchema.StringPropertyConfig.IndexingType
private static int convertTermMatchTypeFromProto(@NonNull TermMatchType.Code termMatchType) {
switch (termMatchType) {
case UNKNOWN:
return AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE;
case EXACT_ONLY:
return AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS;
case PREFIX:
return AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES;
default:
// Avoid crashing in the 'read' path; we should try to interpret the document to the
// extent possible.
Log.w(TAG, "Invalid indexingType: " + termMatchType.getNumber());
return AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE;
}
}
@NonNull
private static StringIndexingConfig.TokenizerType.Code convertTokenizerTypeToProto(
@AppSearchSchema.StringPropertyConfig.TokenizerType int tokenizerType) {
StringIndexingConfig.TokenizerType.Code tokenizerTypeProto =
StringIndexingConfig.TokenizerType.Code.forNumber(tokenizerType);
if (tokenizerTypeProto == null) {
throw new IllegalArgumentException("Invalid tokenizerType: " + tokenizerType);
}
return tokenizerTypeProto;
}
}

View File

@@ -1,141 +0,0 @@
/*
* Copyright 2020 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.external.localstorage.converter;
import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.createPrefix;
import android.annotation.NonNull;
import android.app.appsearch.GenericDocument;
import android.app.appsearch.SearchResult;
import android.app.appsearch.SearchResultPage;
import android.os.Bundle;
import com.android.internal.util.Preconditions;
import com.google.android.icing.proto.SchemaTypeConfigProto;
import com.google.android.icing.proto.SearchResultProto;
import com.google.android.icing.proto.SearchResultProtoOrBuilder;
import com.google.android.icing.proto.SnippetMatchProto;
import com.google.android.icing.proto.SnippetProto;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Translates a {@link SearchResultProto} into {@link SearchResult}s.
*
* @hide
*/
public class SearchResultToProtoConverter {
private SearchResultToProtoConverter() {}
/**
* Translate a {@link SearchResultProto} into {@link SearchResultPage}.
*
* @param proto The {@link SearchResultProto} containing results.
* @param packageNames A parallel array of package names. The package name at index 'i' of this
* list should be the package that indexed the document at index 'i' of proto.getResults(i).
* @param databaseNames A parallel array of database names. The database name at index 'i' of
* this list shold be the database that indexed the document at index 'i' of
* proto.getResults(i).
* @param schemaMap A map of prefixes to an inner-map of prefixed schema type to
* SchemaTypeConfigProtos, used for setting a default value for results with DocumentProtos
* that have empty values.
* @return {@link SearchResultPage} of results.
*/
@NonNull
public static SearchResultPage toSearchResultPage(
@NonNull SearchResultProtoOrBuilder proto,
@NonNull List<String> packageNames,
@NonNull List<String> databaseNames,
@NonNull Map<String, Map<String, SchemaTypeConfigProto>> schemaMap) {
Preconditions.checkArgument(
proto.getResultsCount() == packageNames.size(),
"Size of results does not match the number of package names.");
Bundle bundle = new Bundle();
bundle.putLong(SearchResultPage.NEXT_PAGE_TOKEN_FIELD, proto.getNextPageToken());
ArrayList<Bundle> resultBundles = new ArrayList<>(proto.getResultsCount());
for (int i = 0; i < proto.getResultsCount(); i++) {
String prefix = createPrefix(packageNames.get(i), databaseNames.get(i));
Map<String, SchemaTypeConfigProto> schemaTypeMap = schemaMap.get(prefix);
SearchResult result =
toSearchResult(
proto.getResults(i),
packageNames.get(i),
databaseNames.get(i),
schemaTypeMap);
resultBundles.add(result.getBundle());
}
bundle.putParcelableArrayList(SearchResultPage.RESULTS_FIELD, resultBundles);
return new SearchResultPage(bundle);
}
/**
* Translate a {@link SearchResultProto.ResultProto} into {@link SearchResult}.
*
* @param proto The proto to be converted.
* @param packageName The package name associated with the document in {@code proto}.
* @param databaseName The database name associated with the document in {@code proto}.
* @param schemaTypeToProtoMap A map of prefixed schema types to their corresponding
* SchemaTypeConfigProto, used for setting a default value for results with DocumentProtos
* that have empty values.
* @return A {@link SearchResult} bundle.
*/
@NonNull
private static SearchResult toSearchResult(
@NonNull SearchResultProto.ResultProtoOrBuilder proto,
@NonNull String packageName,
@NonNull String databaseName,
@NonNull Map<String, SchemaTypeConfigProto> schemaTypeToProtoMap) {
String prefix = createPrefix(packageName, databaseName);
GenericDocument document =
GenericDocumentToProtoConverter.toGenericDocument(
proto.getDocument(), prefix, schemaTypeToProtoMap);
SearchResult.Builder builder =
new SearchResult.Builder(packageName, databaseName)
.setGenericDocument(document)
.setRankingSignal(proto.getScore());
if (proto.hasSnippet()) {
for (int i = 0; i < proto.getSnippet().getEntriesCount(); i++) {
SnippetProto.EntryProto entry = proto.getSnippet().getEntries(i);
for (int j = 0; j < entry.getSnippetMatchesCount(); j++) {
SearchResult.MatchInfo matchInfo =
toMatchInfo(entry.getSnippetMatches(j), entry.getPropertyName());
builder.addMatchInfo(matchInfo);
}
}
}
return builder.build();
}
private static SearchResult.MatchInfo toMatchInfo(
@NonNull SnippetMatchProto snippetMatchProto, @NonNull String propertyPath) {
return new SearchResult.MatchInfo.Builder(propertyPath)
.setExactMatchRange(
new SearchResult.MatchRange(
snippetMatchProto.getExactMatchUtf16Position(),
snippetMatchProto.getExactMatchUtf16Position()
+ snippetMatchProto.getExactMatchUtf16Length()))
.setSnippetRange(
new SearchResult.MatchRange(
snippetMatchProto.getWindowUtf16Position(),
snippetMatchProto.getWindowUtf16Position()
+ snippetMatchProto.getWindowUtf16Length()))
.build();
}
}

View File

@@ -1,116 +0,0 @@
/*
* Copyright 2020 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.external.localstorage.converter;
import android.annotation.NonNull;
import android.app.appsearch.SearchSpec;
import com.google.android.icing.proto.ResultSpecProto;
import com.google.android.icing.proto.ScoringSpecProto;
import com.google.android.icing.proto.SearchSpecProto;
import com.google.android.icing.proto.TermMatchType;
import java.util.Objects;
/**
* Translates a {@link SearchSpec} into icing search protos.
*
* @hide
*/
public final class SearchSpecToProtoConverter {
private SearchSpecToProtoConverter() {}
/** Extracts {@link SearchSpecProto} information from a {@link SearchSpec}. */
@NonNull
public static SearchSpecProto toSearchSpecProto(@NonNull SearchSpec spec) {
Objects.requireNonNull(spec);
SearchSpecProto.Builder protoBuilder =
SearchSpecProto.newBuilder()
.addAllSchemaTypeFilters(spec.getFilterSchemas())
.addAllNamespaceFilters(spec.getFilterNamespaces());
@SearchSpec.TermMatch int termMatchCode = spec.getTermMatch();
TermMatchType.Code termMatchCodeProto = TermMatchType.Code.forNumber(termMatchCode);
if (termMatchCodeProto == null || termMatchCodeProto.equals(TermMatchType.Code.UNKNOWN)) {
throw new IllegalArgumentException("Invalid term match type: " + termMatchCode);
}
protoBuilder.setTermMatchType(termMatchCodeProto);
return protoBuilder.build();
}
/** Extracts {@link ResultSpecProto} information from a {@link SearchSpec}. */
@NonNull
public static ResultSpecProto toResultSpecProto(@NonNull SearchSpec spec) {
Objects.requireNonNull(spec);
return ResultSpecProto.newBuilder()
.setNumPerPage(spec.getResultCountPerPage())
.setSnippetSpec(
ResultSpecProto.SnippetSpecProto.newBuilder()
.setNumToSnippet(spec.getSnippetCount())
.setNumMatchesPerProperty(spec.getSnippetCountPerProperty())
.setMaxWindowBytes(spec.getMaxSnippetSize()))
.addAllTypePropertyMasks(
TypePropertyPathToProtoConverter.toTypePropertyMaskList(
spec.getProjections()))
.build();
}
/** Extracts {@link ScoringSpecProto} information from a {@link SearchSpec}. */
@NonNull
public static ScoringSpecProto toScoringSpecProto(@NonNull SearchSpec spec) {
Objects.requireNonNull(spec);
ScoringSpecProto.Builder protoBuilder = ScoringSpecProto.newBuilder();
@SearchSpec.Order int orderCode = spec.getOrder();
ScoringSpecProto.Order.Code orderCodeProto =
ScoringSpecProto.Order.Code.forNumber(orderCode);
if (orderCodeProto == null) {
throw new IllegalArgumentException("Invalid result ranking order: " + orderCode);
}
protoBuilder
.setOrderBy(orderCodeProto)
.setRankBy(toProtoRankingStrategy(spec.getRankingStrategy()));
return protoBuilder.build();
}
private static ScoringSpecProto.RankingStrategy.Code toProtoRankingStrategy(
@SearchSpec.RankingStrategy int rankingStrategyCode) {
switch (rankingStrategyCode) {
case SearchSpec.RANKING_STRATEGY_NONE:
return ScoringSpecProto.RankingStrategy.Code.NONE;
case SearchSpec.RANKING_STRATEGY_DOCUMENT_SCORE:
return ScoringSpecProto.RankingStrategy.Code.DOCUMENT_SCORE;
case SearchSpec.RANKING_STRATEGY_CREATION_TIMESTAMP:
return ScoringSpecProto.RankingStrategy.Code.CREATION_TIMESTAMP;
case SearchSpec.RANKING_STRATEGY_RELEVANCE_SCORE:
return ScoringSpecProto.RankingStrategy.Code.RELEVANCE_SCORE;
case SearchSpec.RANKING_STRATEGY_USAGE_COUNT:
return ScoringSpecProto.RankingStrategy.Code.USAGE_TYPE1_COUNT;
case SearchSpec.RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP:
return ScoringSpecProto.RankingStrategy.Code.USAGE_TYPE1_LAST_USED_TIMESTAMP;
case SearchSpec.RANKING_STRATEGY_SYSTEM_USAGE_COUNT:
return ScoringSpecProto.RankingStrategy.Code.USAGE_TYPE2_COUNT;
case SearchSpec.RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP:
return ScoringSpecProto.RankingStrategy.Code.USAGE_TYPE2_LAST_USED_TIMESTAMP;
default:
throw new IllegalArgumentException(
"Invalid result ranking strategy: " + rankingStrategyCode);
}
}
}

View File

@@ -1,60 +0,0 @@
/*
* Copyright 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.external.localstorage.converter;
import android.annotation.NonNull;
import android.app.appsearch.SetSchemaResponse;
import com.google.android.icing.proto.SetSchemaResultProto;
import java.util.Objects;
/**
* Translates a {@link SetSchemaResultProto} into {@link SetSchemaResponse}.
*
* @hide
*/
public class SetSchemaResponseToProtoConverter {
private SetSchemaResponseToProtoConverter() {}
/**
* Translate a {@link SetSchemaResultProto} into {@link SetSchemaResponse}.
*
* @param proto The {@link SetSchemaResultProto} containing results.
* @param prefix The prefix need to removed from schemaTypes
* @return The {@link SetSchemaResponse} object.
*/
@NonNull
public static SetSchemaResponse toSetSchemaResponse(
@NonNull SetSchemaResultProto proto, @NonNull String prefix) {
Objects.requireNonNull(proto);
Objects.requireNonNull(prefix);
SetSchemaResponse.Builder builder = new SetSchemaResponse.Builder();
for (int i = 0; i < proto.getDeletedSchemaTypesCount(); i++) {
builder.addDeletedType(proto.getDeletedSchemaTypes(i).substring(prefix.length()));
}
for (int i = 0; i < proto.getIncompatibleSchemaTypesCount(); i++) {
builder.addIncompatibleType(
proto.getIncompatibleSchemaTypes(i).substring(prefix.length()));
}
return builder.build();
}
}

View File

@@ -1,51 +0,0 @@
/*
* Copyright 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.external.localstorage.converter;
import android.annotation.NonNull;
import com.google.android.icing.proto.TypePropertyMask;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* Translates a <code>Map<String, List<String>></code> into <code>List<TypePropertyMask></code>.
*
* @hide
*/
public final class TypePropertyPathToProtoConverter {
private TypePropertyPathToProtoConverter() {}
/** Extracts {@link TypePropertyMask} information from a {@link Map}. */
@NonNull
public static List<TypePropertyMask> toTypePropertyMaskList(
@NonNull Map<String, List<String>> typePropertyPaths) {
Objects.requireNonNull(typePropertyPaths);
List<TypePropertyMask> typePropertyMasks = new ArrayList<>(typePropertyPaths.size());
for (Map.Entry<String, List<String>> e : typePropertyPaths.entrySet()) {
typePropertyMasks.add(
TypePropertyMask.newBuilder()
.setSchemaType(e.getKey())
.addAllPaths(e.getValue())
.build());
}
return typePropertyMasks;
}
}

View File

@@ -1,262 +0,0 @@
/*
* Copyright 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.external.localstorage.stats;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.appsearch.AppSearchResult;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/**
* A class for setting basic information to log for all function calls.
*
* <p>This class can set which stats to log for both batch and non-batch {@link
* android.app.appsearch.AppSearchSession} calls.
*
* <p>Some function calls may have their own detailed stats class like {@link PutDocumentStats}.
* However, {@link CallStats} can still be used along with the detailed stats class for easy
* aggregation/analysis with other function calls.
*
* @hide
*/
public class CallStats {
@IntDef(
value = {
CALL_TYPE_UNKNOWN,
CALL_TYPE_INITIALIZE,
CALL_TYPE_SET_SCHEMA,
CALL_TYPE_PUT_DOCUMENTS,
CALL_TYPE_GET_DOCUMENTS,
CALL_TYPE_REMOVE_DOCUMENTS_BY_ID,
CALL_TYPE_PUT_DOCUMENT,
CALL_TYPE_GET_DOCUMENT,
CALL_TYPE_REMOVE_DOCUMENT_BY_ID,
CALL_TYPE_SEARCH,
CALL_TYPE_OPTIMIZE,
CALL_TYPE_FLUSH,
CALL_TYPE_GLOBAL_SEARCH,
CALL_TYPE_REMOVE_DOCUMENTS_BY_SEARCH,
CALL_TYPE_REMOVE_DOCUMENT_BY_SEARCH,
})
@Retention(RetentionPolicy.SOURCE)
public @interface CallType {}
public static final int CALL_TYPE_UNKNOWN = 0;
public static final int CALL_TYPE_INITIALIZE = 1;
public static final int CALL_TYPE_SET_SCHEMA = 2;
public static final int CALL_TYPE_PUT_DOCUMENTS = 3;
public static final int CALL_TYPE_GET_DOCUMENTS = 4;
public static final int CALL_TYPE_REMOVE_DOCUMENTS_BY_ID = 5;
public static final int CALL_TYPE_PUT_DOCUMENT = 6;
public static final int CALL_TYPE_GET_DOCUMENT = 7;
public static final int CALL_TYPE_REMOVE_DOCUMENT_BY_ID = 8;
public static final int CALL_TYPE_SEARCH = 9;
public static final int CALL_TYPE_OPTIMIZE = 10;
public static final int CALL_TYPE_FLUSH = 11;
public static final int CALL_TYPE_GLOBAL_SEARCH = 12;
public static final int CALL_TYPE_REMOVE_DOCUMENTS_BY_SEARCH = 13;
public static final int CALL_TYPE_REMOVE_DOCUMENT_BY_SEARCH = 14;
@Nullable private final String mPackageName;
@Nullable private final String mDatabase;
/**
* The status code returned by {@link AppSearchResult#getResultCode()} for the call or internal
* state.
*/
@AppSearchResult.ResultCode private final int mStatusCode;
private final int mTotalLatencyMillis;
@CallType private final int mCallType;
private final int mEstimatedBinderLatencyMillis;
private final int mNumOperationsSucceeded;
private final int mNumOperationsFailed;
CallStats(@NonNull Builder builder) {
Objects.requireNonNull(builder);
mPackageName = builder.mPackageName;
mDatabase = builder.mDatabase;
mStatusCode = builder.mStatusCode;
mTotalLatencyMillis = builder.mTotalLatencyMillis;
mCallType = builder.mCallType;
mEstimatedBinderLatencyMillis = builder.mEstimatedBinderLatencyMillis;
mNumOperationsSucceeded = builder.mNumOperationsSucceeded;
mNumOperationsFailed = builder.mNumOperationsFailed;
}
/** Returns calling package name. */
@Nullable
public String getPackageName() {
return mPackageName;
}
/** Returns calling database name. */
@Nullable
public String getDatabase() {
return mDatabase;
}
/** Returns status code for this api call. */
@AppSearchResult.ResultCode
public int getStatusCode() {
return mStatusCode;
}
/** Returns total latency of this api call in millis. */
public int getTotalLatencyMillis() {
return mTotalLatencyMillis;
}
/** Returns type of the call. */
@CallType
public int getCallType() {
return mCallType;
}
/** Returns estimated binder latency, in milliseconds */
public int getEstimatedBinderLatencyMillis() {
return mEstimatedBinderLatencyMillis;
}
/**
* Returns number of operations succeeded.
*
* <p>For example, for {@link android.app.appsearch.AppSearchSession#put}, it is the total
* number of individual successful put operations. In this case, how many documents are
* successfully indexed.
*
* <p>For non-batch calls such as {@link android.app.appsearch.AppSearchSession#setSchema}, the
* sum of {@link CallStats#getNumOperationsSucceeded()} and {@link
* CallStats#getNumOperationsFailed()} is always 1 since there is only one operation.
*/
public int getNumOperationsSucceeded() {
return mNumOperationsSucceeded;
}
/**
* Returns number of operations failed.
*
* <p>For example, for {@link android.app.appsearch.AppSearchSession#put}, it is the total
* number of individual failed put operations. In this case, how many documents are failed to be
* indexed.
*
* <p>For non-batch calls such as {@link android.app.appsearch.AppSearchSession#setSchema}, the
* sum of {@link CallStats#getNumOperationsSucceeded()} and {@link
* CallStats#getNumOperationsFailed()} is always 1 since there is only one operation.
*/
public int getNumOperationsFailed() {
return mNumOperationsFailed;
}
/** Builder for {@link CallStats}. */
public static class Builder {
@Nullable String mPackageName;
@Nullable String mDatabase;
@AppSearchResult.ResultCode int mStatusCode;
int mTotalLatencyMillis;
@CallType int mCallType;
int mEstimatedBinderLatencyMillis;
int mNumOperationsSucceeded;
int mNumOperationsFailed;
/** Sets the PackageName used by the session. */
@NonNull
public Builder setPackageName(@NonNull String packageName) {
mPackageName = Objects.requireNonNull(packageName);
return this;
}
/** Sets the database used by the session. */
@NonNull
public Builder setDatabase(@NonNull String database) {
mDatabase = Objects.requireNonNull(database);
return this;
}
/** Sets the status code. */
@NonNull
public Builder setStatusCode(@AppSearchResult.ResultCode int statusCode) {
mStatusCode = statusCode;
return this;
}
/** Sets total latency in millis. */
@NonNull
public Builder setTotalLatencyMillis(int totalLatencyMillis) {
mTotalLatencyMillis = totalLatencyMillis;
return this;
}
/** Sets type of the call. */
@NonNull
public Builder setCallType(@CallType int callType) {
mCallType = callType;
return this;
}
/** Sets estimated binder latency, in milliseconds. */
@NonNull
public Builder setEstimatedBinderLatencyMillis(int estimatedBinderLatencyMillis) {
mEstimatedBinderLatencyMillis = estimatedBinderLatencyMillis;
return this;
}
/**
* Sets number of operations succeeded.
*
* <p>For example, for {@link android.app.appsearch.AppSearchSession#put}, it is the total
* number of individual successful put operations. In this case, how many documents are
* successfully indexed.
*
* <p>For non-batch calls such as {@link android.app.appsearch.AppSearchSession#setSchema},
* the sum of {@link CallStats#getNumOperationsSucceeded()} and {@link
* CallStats#getNumOperationsFailed()} is always 1 since there is only one operation.
*/
@NonNull
public Builder setNumOperationsSucceeded(int numOperationsSucceeded) {
mNumOperationsSucceeded = numOperationsSucceeded;
return this;
}
/**
* Sets number of operations failed.
*
* <p>For example, for {@link android.app.appsearch.AppSearchSession#put}, it is the total
* number of individual failed put operations. In this case, how many documents are failed
* to be indexed.
*
* <p>For non-batch calls such as {@link android.app.appsearch.AppSearchSession#setSchema},
* the sum of {@link CallStats#getNumOperationsSucceeded()} and {@link
* CallStats#getNumOperationsFailed()} is always 1 since there is only one operation.
*/
@NonNull
public Builder setNumOperationsFailed(int numOperationsFailed) {
mNumOperationsFailed = numOperationsFailed;
return this;
}
/** Creates {@link CallStats} object from {@link Builder} instance. */
@NonNull
public CallStats build() {
return new CallStats(/* builder= */ this);
}
}
}

View File

@@ -1,442 +0,0 @@
/*
* Copyright 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.external.localstorage.stats;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.app.appsearch.AppSearchResult;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/**
* Class holds detailed stats for initialization
*
* @hide
*/
public final class InitializeStats {
/**
* The cause of IcingSearchEngine recovering from a previous bad state during initialization.
*/
@IntDef(
value = {
// It needs to be sync with RecoveryCause in
// external/icing/proto/icing/proto/logging.proto#InitializeStatsProto
RECOVERY_CAUSE_NONE,
RECOVERY_CAUSE_DATA_LOSS,
RECOVERY_CAUSE_INCONSISTENT_WITH_GROUND_TRUTH,
RECOVERY_CAUSE_TOTAL_CHECKSUM_MISMATCH,
RECOVERY_CAUSE_IO_ERROR,
})
@Retention(RetentionPolicy.SOURCE)
public @interface RecoveryCause {}
// No recovery happened.
public static final int RECOVERY_CAUSE_NONE = 0;
// Data loss in ground truth.
public static final int RECOVERY_CAUSE_DATA_LOSS = 1;
// Data in index is inconsistent with ground truth.
public static final int RECOVERY_CAUSE_INCONSISTENT_WITH_GROUND_TRUTH = 2;
// Total checksum of all the components does not match.
public static final int RECOVERY_CAUSE_TOTAL_CHECKSUM_MISMATCH = 3;
// Random I/O errors.
public static final int RECOVERY_CAUSE_IO_ERROR = 4;
/** Status regarding how much data is lost during the initialization. */
@IntDef(
value = {
// It needs to be sync with DocumentStoreDataStatus in
// external/icing/proto/icing/proto/logging.proto#InitializeStatsProto
DOCUMENT_STORE_DATA_STATUS_NO_DATA_LOSS,
DOCUMENT_STORE_DATA_STATUS_PARTIAL_LOSS,
DOCUMENT_STORE_DATA_STATUS_COMPLETE_LOSS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DocumentStoreDataStatus {}
// Document store is successfully initialized or fully recovered.
public static final int DOCUMENT_STORE_DATA_STATUS_NO_DATA_LOSS = 0;
// Ground truth data is partially lost.
public static final int DOCUMENT_STORE_DATA_STATUS_PARTIAL_LOSS = 1;
// Ground truth data is completely lost.
public static final int DOCUMENT_STORE_DATA_STATUS_COMPLETE_LOSS = 2;
@AppSearchResult.ResultCode private final int mStatusCode;
private final int mTotalLatencyMillis;
/** Whether the initialize() detects deSync. */
private final boolean mHasDeSync;
/** Time used to read and process the schema and namespaces. */
private final int mPrepareSchemaAndNamespacesLatencyMillis;
/** Time used to read and process the visibility store. */
private final int mPrepareVisibilityStoreLatencyMillis;
/** Overall time used for the native function call. */
private final int mNativeLatencyMillis;
@RecoveryCause private final int mNativeDocumentStoreRecoveryCause;
@RecoveryCause private final int mNativeIndexRestorationCause;
@RecoveryCause private final int mNativeSchemaStoreRecoveryCause;
/** Time used to recover the document store. */
private final int mNativeDocumentStoreRecoveryLatencyMillis;
/** Time used to restore the index. */
private final int mNativeIndexRestorationLatencyMillis;
/** Time used to recover the schema store. */
private final int mNativeSchemaStoreRecoveryLatencyMillis;
/** Status regarding how much data is lost during the initialization. */
private final int mNativeDocumentStoreDataStatus;
/**
* Returns number of documents currently in document store. Those may include alive, deleted,
* and expired documents.
*/
private final int mNativeNumDocuments;
/** Returns number of schema types currently in the schema store. */
private final int mNativeNumSchemaTypes;
/** Whether we had to reset the index, losing all data, during initialization. */
private final boolean mHasReset;
/** If we had to reset, contains the status code of the reset operation. */
@AppSearchResult.ResultCode private final int mResetStatusCode;
/** Returns the status of the initialization. */
@AppSearchResult.ResultCode
public int getStatusCode() {
return mStatusCode;
}
/** Returns the total latency in milliseconds for the initialization. */
public int getTotalLatencyMillis() {
return mTotalLatencyMillis;
}
/**
* Returns whether the initialize() detects deSync.
*
* <p>If there is a deSync, it means AppSearch and IcingSearchEngine have an inconsistent view
* of what data should exist.
*/
public boolean hasDeSync() {
return mHasDeSync;
}
/** Returns time used to read and process the schema and namespaces. */
public int getPrepareSchemaAndNamespacesLatencyMillis() {
return mPrepareSchemaAndNamespacesLatencyMillis;
}
/** Returns time used to read and process the visibility file. */
public int getPrepareVisibilityStoreLatencyMillis() {
return mPrepareVisibilityStoreLatencyMillis;
}
/** Returns overall time used for the native function call. */
public int getNativeLatencyMillis() {
return mNativeLatencyMillis;
}
/**
* Returns recovery cause for document store.
*
* <p>Possible recovery causes for document store:
* <li>{@link InitializeStats#RECOVERY_CAUSE_DATA_LOSS}
* <li>{@link InitializeStats#RECOVERY_CAUSE_TOTAL_CHECKSUM_MISMATCH}
* <li>{@link InitializeStats#RECOVERY_CAUSE_IO_ERROR}
*/
@RecoveryCause
public int getDocumentStoreRecoveryCause() {
return mNativeDocumentStoreRecoveryCause;
}
/**
* Returns restoration cause for index store.
*
* <p>Possible causes:
* <li>{@link InitializeStats#RECOVERY_CAUSE_INCONSISTENT_WITH_GROUND_TRUTH}
* <li>{@link InitializeStats#RECOVERY_CAUSE_TOTAL_CHECKSUM_MISMATCH}
* <li>{@link InitializeStats#RECOVERY_CAUSE_IO_ERROR}
*/
@RecoveryCause
public int getIndexRestorationCause() {
return mNativeIndexRestorationCause;
}
/**
* Returns recovery cause for schema store.
*
* <p>Possible causes:
* <li>IO_ERROR
*/
@RecoveryCause
public int getSchemaStoreRecoveryCause() {
return mNativeSchemaStoreRecoveryCause;
}
/** Returns time used to recover the document store. */
public int getDocumentStoreRecoveryLatencyMillis() {
return mNativeDocumentStoreRecoveryLatencyMillis;
}
/** Returns time used to restore the index. */
public int getIndexRestorationLatencyMillis() {
return mNativeIndexRestorationLatencyMillis;
}
/** Returns time used to recover the schema store. */
public int getSchemaStoreRecoveryLatencyMillis() {
return mNativeSchemaStoreRecoveryLatencyMillis;
}
/** Returns status about how much data is lost during the initialization. */
@DocumentStoreDataStatus
public int getDocumentStoreDataStatus() {
return mNativeDocumentStoreDataStatus;
}
/**
* Returns number of documents currently in document store. Those may include alive, deleted,
* and expired documents.
*/
public int getDocumentCount() {
return mNativeNumDocuments;
}
/** Returns number of schema types currently in the schema store. */
public int getSchemaTypeCount() {
return mNativeNumSchemaTypes;
}
/** Returns whether we had to reset the index, losing all data, as part of initialization. */
public boolean hasReset() {
return mHasReset;
}
/**
* Returns the status of the reset, if one was performed according to {@link #hasReset}.
*
* <p>If no value has been set, the default value is {@link AppSearchResult#RESULT_OK}.
*/
@AppSearchResult.ResultCode
public int getResetStatusCode() {
return mResetStatusCode;
}
InitializeStats(@NonNull Builder builder) {
Objects.requireNonNull(builder);
mStatusCode = builder.mStatusCode;
mTotalLatencyMillis = builder.mTotalLatencyMillis;
mHasDeSync = builder.mHasDeSync;
mPrepareSchemaAndNamespacesLatencyMillis = builder.mPrepareSchemaAndNamespacesLatencyMillis;
mPrepareVisibilityStoreLatencyMillis = builder.mPrepareVisibilityStoreLatencyMillis;
mNativeLatencyMillis = builder.mNativeLatencyMillis;
mNativeDocumentStoreRecoveryCause = builder.mNativeDocumentStoreRecoveryCause;
mNativeIndexRestorationCause = builder.mNativeIndexRestorationCause;
mNativeSchemaStoreRecoveryCause = builder.mNativeSchemaStoreRecoveryCause;
mNativeDocumentStoreRecoveryLatencyMillis =
builder.mNativeDocumentStoreRecoveryLatencyMillis;
mNativeIndexRestorationLatencyMillis = builder.mNativeIndexRestorationLatencyMillis;
mNativeSchemaStoreRecoveryLatencyMillis = builder.mNativeSchemaStoreRecoveryLatencyMillis;
mNativeDocumentStoreDataStatus = builder.mNativeDocumentStoreDataStatus;
mNativeNumDocuments = builder.mNativeNumDocuments;
mNativeNumSchemaTypes = builder.mNativeNumSchemaTypes;
mHasReset = builder.mHasReset;
mResetStatusCode = builder.mResetStatusCode;
}
/** Builder for {@link InitializeStats}. */
public static class Builder {
@AppSearchResult.ResultCode int mStatusCode;
int mTotalLatencyMillis;
boolean mHasDeSync;
int mPrepareSchemaAndNamespacesLatencyMillis;
int mPrepareVisibilityStoreLatencyMillis;
int mNativeLatencyMillis;
@RecoveryCause int mNativeDocumentStoreRecoveryCause;
@RecoveryCause int mNativeIndexRestorationCause;
@RecoveryCause int mNativeSchemaStoreRecoveryCause;
int mNativeDocumentStoreRecoveryLatencyMillis;
int mNativeIndexRestorationLatencyMillis;
int mNativeSchemaStoreRecoveryLatencyMillis;
@DocumentStoreDataStatus int mNativeDocumentStoreDataStatus;
int mNativeNumDocuments;
int mNativeNumSchemaTypes;
boolean mHasReset;
@AppSearchResult.ResultCode int mResetStatusCode;
/** Sets the status of the initialization. */
@NonNull
public Builder setStatusCode(@AppSearchResult.ResultCode int statusCode) {
mStatusCode = statusCode;
return this;
}
/** Sets the total latency of the initialization in milliseconds. */
@NonNull
public Builder setTotalLatencyMillis(int totalLatencyMillis) {
mTotalLatencyMillis = totalLatencyMillis;
return this;
}
/**
* Sets whether the initialize() detects deSync.
*
* <p>If there is a deSync, it means AppSearch and IcingSearchEngine have an inconsistent
* view of what data should exist.
*/
@NonNull
public Builder setHasDeSync(boolean hasDeSync) {
mHasDeSync = hasDeSync;
return this;
}
/** Sets time used to read and process the schema and namespaces. */
@NonNull
public Builder setPrepareSchemaAndNamespacesLatencyMillis(
int prepareSchemaAndNamespacesLatencyMillis) {
mPrepareSchemaAndNamespacesLatencyMillis = prepareSchemaAndNamespacesLatencyMillis;
return this;
}
/** Sets time used to read and process the visibility file. */
@NonNull
public Builder setPrepareVisibilityStoreLatencyMillis(
int prepareVisibilityStoreLatencyMillis) {
mPrepareVisibilityStoreLatencyMillis = prepareVisibilityStoreLatencyMillis;
return this;
}
/** Sets overall time used for the native function call. */
@NonNull
public Builder setNativeLatencyMillis(int nativeLatencyMillis) {
mNativeLatencyMillis = nativeLatencyMillis;
return this;
}
/**
* Sets recovery cause for document store.
*
* <p>Possible recovery causes for document store:
* <li>{@link InitializeStats#RECOVERY_CAUSE_DATA_LOSS}
* <li>{@link InitializeStats#RECOVERY_CAUSE_TOTAL_CHECKSUM_MISMATCH}
* <li>{@link InitializeStats#RECOVERY_CAUSE_IO_ERROR}
*/
@NonNull
public Builder setDocumentStoreRecoveryCause(
@RecoveryCause int documentStoreRecoveryCause) {
mNativeDocumentStoreRecoveryCause = documentStoreRecoveryCause;
return this;
}
/**
* Sets restoration cause for index store.
*
* <p>Possible causes:
* <li>{@link InitializeStats#DOCUMENT_STORE_DATA_STATUS_COMPLETE_LOSS}
* <li>{@link InitializeStats#RECOVERY_CAUSE_TOTAL_CHECKSUM_MISMATCH}
* <li>{@link InitializeStats#RECOVERY_CAUSE_IO_ERROR}
*/
@NonNull
public Builder setIndexRestorationCause(@RecoveryCause int indexRestorationCause) {
mNativeIndexRestorationCause = indexRestorationCause;
return this;
}
/**
* Returns recovery cause for schema store.
*
* <p>Possible causes:
* <li>{@link InitializeStats#RECOVERY_CAUSE_IO_ERROR}
*/
@NonNull
public Builder setSchemaStoreRecoveryCause(@RecoveryCause int schemaStoreRecoveryCause) {
mNativeSchemaStoreRecoveryCause = schemaStoreRecoveryCause;
return this;
}
/** Sets time used to recover the document store. */
@NonNull
public Builder setDocumentStoreRecoveryLatencyMillis(
int documentStoreRecoveryLatencyMillis) {
mNativeDocumentStoreRecoveryLatencyMillis = documentStoreRecoveryLatencyMillis;
return this;
}
/** Sets time used to restore the index. */
@NonNull
public Builder setIndexRestorationLatencyMillis(int indexRestorationLatencyMillis) {
mNativeIndexRestorationLatencyMillis = indexRestorationLatencyMillis;
return this;
}
/** Sets time used to recover the schema store. */
@NonNull
public Builder setSchemaStoreRecoveryLatencyMillis(int schemaStoreRecoveryLatencyMillis) {
mNativeSchemaStoreRecoveryLatencyMillis = schemaStoreRecoveryLatencyMillis;
return this;
}
/**
* Sets Native Document Store Data status. status is defined in
* external/icing/proto/icing/proto/logging.proto
*/
@NonNull
public Builder setDocumentStoreDataStatus(
@DocumentStoreDataStatus int documentStoreDataStatus) {
mNativeDocumentStoreDataStatus = documentStoreDataStatus;
return this;
}
/**
* Sets number of documents currently in document store. Those may include alive, deleted,
* and expired documents.
*/
@NonNull
public Builder setDocumentCount(int numDocuments) {
mNativeNumDocuments = numDocuments;
return this;
}
/** Sets number of schema types currently in the schema store. */
@NonNull
public Builder setSchemaTypeCount(int numSchemaTypes) {
mNativeNumSchemaTypes = numSchemaTypes;
return this;
}
/** Sets whether we had to reset the index, losing all data, as part of initialization. */
@NonNull
public Builder setHasReset(boolean hasReset) {
mHasReset = hasReset;
return this;
}
/** Sets the status of the reset, if one was performed according to {@link #setHasReset}. */
@NonNull
public Builder setResetStatusCode(@AppSearchResult.ResultCode int resetStatusCode) {
mResetStatusCode = resetStatusCode;
return this;
}
/**
* Constructs a new {@link InitializeStats} from the contents of this {@link
* InitializeStats.Builder}
*/
@NonNull
public InitializeStats build() {
return new InitializeStats(/* builder= */ this);
}
}
}

View File

@@ -1,243 +0,0 @@
/*
* Copyright 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.external.localstorage.stats;
import android.annotation.NonNull;
import android.app.appsearch.AppSearchResult;
import java.util.Objects;
/**
* Class holds detailed stats for Optimize.
*
* @hide
*/
public final class OptimizeStats {
/**
* The status code returned by {@link AppSearchResult#getResultCode()} for the call or internal
* state.
*/
@AppSearchResult.ResultCode private final int mStatusCode;
private final int mTotalLatencyMillis;
private final int mNativeLatencyMillis;
// Time used to optimize the document store in millis.
private final int mNativeDocumentStoreOptimizeLatencyMillis;
// Time used to restore the index in millis.
private final int mNativeIndexRestorationLatencyMillis;
// Number of documents before the optimization.
private final int mNativeOriginalDocumentCount;
// Number of documents deleted during the optimization.
private final int mNativeDeletedDocumentCount;
// Number of documents expired during the optimization.
private final int mNativeExpiredDocumentCount;
// Size of storage in bytes before the optimization.
private final long mNativeStorageSizeBeforeBytes;
// Size of storage in bytes after the optimization.
private final long mNativeStorageSizeAfterBytes;
// The amount of time in millis since the last optimization ran calculated using wall clock time
private final long mNativeTimeSinceLastOptimizeMillis;
OptimizeStats(@NonNull Builder builder) {
Objects.requireNonNull(builder);
mStatusCode = builder.mStatusCode;
mTotalLatencyMillis = builder.mTotalLatencyMillis;
mNativeLatencyMillis = builder.mNativeLatencyMillis;
mNativeDocumentStoreOptimizeLatencyMillis =
builder.mNativeDocumentStoreOptimizeLatencyMillis;
mNativeIndexRestorationLatencyMillis = builder.mNativeIndexRestorationLatencyMillis;
mNativeOriginalDocumentCount = builder.mNativeOriginalDocumentCount;
mNativeDeletedDocumentCount = builder.mNativeDeletedDocumentCount;
mNativeExpiredDocumentCount = builder.mNativeExpiredDocumentCount;
mNativeStorageSizeBeforeBytes = builder.mNativeStorageSizeBeforeBytes;
mNativeStorageSizeAfterBytes = builder.mNativeStorageSizeAfterBytes;
mNativeTimeSinceLastOptimizeMillis = builder.mNativeTimeSinceLastOptimizeMillis;
}
/** Returns status code for this optimization. */
@AppSearchResult.ResultCode
public int getStatusCode() {
return mStatusCode;
}
/** Returns total latency of this optimization in millis. */
public int getTotalLatencyMillis() {
return mTotalLatencyMillis;
}
/** Returns how much time in millis spent in the native code. */
public int getNativeLatencyMillis() {
return mNativeLatencyMillis;
}
/** Returns time used to optimize the document store in millis. */
public int getDocumentStoreOptimizeLatencyMillis() {
return mNativeDocumentStoreOptimizeLatencyMillis;
}
/** Returns time used to restore the index in millis. */
public int getIndexRestorationLatencyMillis() {
return mNativeIndexRestorationLatencyMillis;
}
/** Returns number of documents before the optimization. */
public int getOriginalDocumentCount() {
return mNativeOriginalDocumentCount;
}
/** Returns number of documents deleted during the optimization. */
public int getDeletedDocumentCount() {
return mNativeDeletedDocumentCount;
}
/** Returns number of documents expired during the optimization. */
public int getExpiredDocumentCount() {
return mNativeExpiredDocumentCount;
}
/** Returns size of storage in bytes before the optimization. */
public long getStorageSizeBeforeBytes() {
return mNativeStorageSizeBeforeBytes;
}
/** Returns size of storage in bytes after the optimization. */
public long getStorageSizeAfterBytes() {
return mNativeStorageSizeAfterBytes;
}
/**
* Returns the amount of time in millis since the last optimization ran calculated using wall
* clock time.
*/
public long getTimeSinceLastOptimizeMillis() {
return mNativeTimeSinceLastOptimizeMillis;
}
/** Builder for {@link RemoveStats}. */
public static class Builder {
/**
* The status code returned by {@link AppSearchResult#getResultCode()} for the call or
* internal state.
*/
@AppSearchResult.ResultCode int mStatusCode;
int mTotalLatencyMillis;
int mNativeLatencyMillis;
int mNativeDocumentStoreOptimizeLatencyMillis;
int mNativeIndexRestorationLatencyMillis;
int mNativeOriginalDocumentCount;
int mNativeDeletedDocumentCount;
int mNativeExpiredDocumentCount;
long mNativeStorageSizeBeforeBytes;
long mNativeStorageSizeAfterBytes;
long mNativeTimeSinceLastOptimizeMillis;
/** Sets the status code. */
@NonNull
public Builder setStatusCode(@AppSearchResult.ResultCode int statusCode) {
mStatusCode = statusCode;
return this;
}
/** Sets total latency in millis. */
@NonNull
public Builder setTotalLatencyMillis(int totalLatencyMillis) {
mTotalLatencyMillis = totalLatencyMillis;
return this;
}
/** Sets native latency in millis. */
@NonNull
public Builder setNativeLatencyMillis(int nativeLatencyMillis) {
mNativeLatencyMillis = nativeLatencyMillis;
return this;
}
/** Sets time used to optimize the document store. */
@NonNull
public Builder setDocumentStoreOptimizeLatencyMillis(
int documentStoreOptimizeLatencyMillis) {
mNativeDocumentStoreOptimizeLatencyMillis = documentStoreOptimizeLatencyMillis;
return this;
}
/** Sets time used to restore the index. */
@NonNull
public Builder setIndexRestorationLatencyMillis(int indexRestorationLatencyMillis) {
mNativeIndexRestorationLatencyMillis = indexRestorationLatencyMillis;
return this;
}
/** Sets number of documents before the optimization. */
@NonNull
public Builder setOriginalDocumentCount(int originalDocumentCount) {
mNativeOriginalDocumentCount = originalDocumentCount;
return this;
}
/** Sets number of documents deleted during the optimization. */
@NonNull
public Builder setDeletedDocumentCount(int deletedDocumentCount) {
mNativeDeletedDocumentCount = deletedDocumentCount;
return this;
}
/** Sets number of documents expired during the optimization. */
@NonNull
public Builder setExpiredDocumentCount(int expiredDocumentCount) {
mNativeExpiredDocumentCount = expiredDocumentCount;
return this;
}
/** Sets Storage size in bytes before optimization. */
@NonNull
public Builder setStorageSizeBeforeBytes(long storageSizeBeforeBytes) {
mNativeStorageSizeBeforeBytes = storageSizeBeforeBytes;
return this;
}
/** Sets storage size in bytes after optimization. */
@NonNull
public Builder setStorageSizeAfterBytes(long storageSizeAfterBytes) {
mNativeStorageSizeAfterBytes = storageSizeAfterBytes;
return this;
}
/**
* Sets the amount the time since the last optimize ran calculated using wall clock time.
*/
@NonNull
public Builder setTimeSinceLastOptimizeMillis(long timeSinceLastOptimizeMillis) {
mNativeTimeSinceLastOptimizeMillis = timeSinceLastOptimizeMillis;
return this;
}
/** Creates a {@link OptimizeStats}. */
@NonNull
public OptimizeStats build() {
return new OptimizeStats(/* builder= */ this);
}
}
}

View File

@@ -1,273 +0,0 @@
/*
* Copyright 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.external.localstorage.stats;
import android.annotation.NonNull;
import android.app.appsearch.AppSearchResult;
import java.util.Objects;
/**
* A class for holding detailed stats to log for each individual document put by a {@link
* android.app.appsearch.AppSearchSession#put} call.
*
* @hide
*/
public final class PutDocumentStats {
@NonNull private final String mPackageName;
@NonNull private final String mDatabase;
/**
* The status code returned by {@link AppSearchResult#getResultCode()} for the call or internal
* state.
*/
@AppSearchResult.ResultCode private final int mStatusCode;
private final int mTotalLatencyMillis;
/** Time used to generate a document proto from a Bundle. */
private final int mGenerateDocumentProtoLatencyMillis;
/** Time used to rewrite types and namespaces in the document. */
private final int mRewriteDocumentTypesLatencyMillis;
/** Overall time used for the native function call. */
private final int mNativeLatencyMillis;
/** Time used to store the document. */
private final int mNativeDocumentStoreLatencyMillis;
/** Time used to index the document. It doesn't include the time to merge indices. */
private final int mNativeIndexLatencyMillis;
/** Time used to merge the indices. */
private final int mNativeIndexMergeLatencyMillis;
/** Document size in bytes. */
private final int mNativeDocumentSizeBytes;
/** Number of tokens added to the index. */
private final int mNativeNumTokensIndexed;
/**
* Whether the number of tokens to be indexed exceeded the max number of tokens per document.
*/
private final boolean mNativeExceededMaxNumTokens;
PutDocumentStats(@NonNull Builder builder) {
Objects.requireNonNull(builder);
mPackageName = builder.mPackageName;
mDatabase = builder.mDatabase;
mStatusCode = builder.mStatusCode;
mTotalLatencyMillis = builder.mTotalLatencyMillis;
mGenerateDocumentProtoLatencyMillis = builder.mGenerateDocumentProtoLatencyMillis;
mRewriteDocumentTypesLatencyMillis = builder.mRewriteDocumentTypesLatencyMillis;
mNativeLatencyMillis = builder.mNativeLatencyMillis;
mNativeDocumentStoreLatencyMillis = builder.mNativeDocumentStoreLatencyMillis;
mNativeIndexLatencyMillis = builder.mNativeIndexLatencyMillis;
mNativeIndexMergeLatencyMillis = builder.mNativeIndexMergeLatencyMillis;
mNativeDocumentSizeBytes = builder.mNativeDocumentSizeBytes;
mNativeNumTokensIndexed = builder.mNativeNumTokensIndexed;
mNativeExceededMaxNumTokens = builder.mNativeExceededMaxNumTokens;
}
/** Returns calling package name. */
@NonNull
public String getPackageName() {
return mPackageName;
}
/** Returns calling database name. */
@NonNull
public String getDatabase() {
return mDatabase;
}
/** Returns status code for this putDocument. */
@AppSearchResult.ResultCode
public int getStatusCode() {
return mStatusCode;
}
/** Returns total latency of this putDocument in millis. */
public int getTotalLatencyMillis() {
return mTotalLatencyMillis;
}
/** Returns time spent on generating document proto, in milliseconds. */
public int getGenerateDocumentProtoLatencyMillis() {
return mGenerateDocumentProtoLatencyMillis;
}
/** Returns time spent on rewriting types and namespaces in document, in milliseconds. */
public int getRewriteDocumentTypesLatencyMillis() {
return mRewriteDocumentTypesLatencyMillis;
}
/** Returns time spent in native, in milliseconds. */
public int getNativeLatencyMillis() {
return mNativeLatencyMillis;
}
/** Returns time spent on document store, in milliseconds. */
public int getNativeDocumentStoreLatencyMillis() {
return mNativeDocumentStoreLatencyMillis;
}
/** Returns time spent on indexing, in milliseconds. */
public int getNativeIndexLatencyMillis() {
return mNativeIndexLatencyMillis;
}
/** Returns time spent on merging indices, in milliseconds. */
public int getNativeIndexMergeLatencyMillis() {
return mNativeIndexMergeLatencyMillis;
}
/** Returns document size, in bytes. */
public int getNativeDocumentSizeBytes() {
return mNativeDocumentSizeBytes;
}
/** Returns number of tokens indexed. */
public int getNativeNumTokensIndexed() {
return mNativeNumTokensIndexed;
}
/**
* Returns whether the number of tokens to be indexed exceeded the max number of tokens per
* document.
*/
public boolean getNativeExceededMaxNumTokens() {
return mNativeExceededMaxNumTokens;
}
/** Builder for {@link PutDocumentStats}. */
public static class Builder {
@NonNull final String mPackageName;
@NonNull final String mDatabase;
@AppSearchResult.ResultCode int mStatusCode;
int mTotalLatencyMillis;
int mGenerateDocumentProtoLatencyMillis;
int mRewriteDocumentTypesLatencyMillis;
int mNativeLatencyMillis;
int mNativeDocumentStoreLatencyMillis;
int mNativeIndexLatencyMillis;
int mNativeIndexMergeLatencyMillis;
int mNativeDocumentSizeBytes;
int mNativeNumTokensIndexed;
boolean mNativeExceededMaxNumTokens;
/** Builder for {@link PutDocumentStats} */
public Builder(@NonNull String packageName, @NonNull String database) {
mPackageName = Objects.requireNonNull(packageName);
mDatabase = Objects.requireNonNull(database);
}
/** Sets the status code. */
@NonNull
public Builder setStatusCode(@AppSearchResult.ResultCode int statusCode) {
mStatusCode = statusCode;
return this;
}
/** Sets total latency in millis. */
@NonNull
public Builder setTotalLatencyMillis(int totalLatencyMillis) {
mTotalLatencyMillis = totalLatencyMillis;
return this;
}
/** Sets how much time we spend for generating document proto, in milliseconds. */
@NonNull
public Builder setGenerateDocumentProtoLatencyMillis(
int generateDocumentProtoLatencyMillis) {
mGenerateDocumentProtoLatencyMillis = generateDocumentProtoLatencyMillis;
return this;
}
/**
* Sets how much time we spend for rewriting types and namespaces in document, in
* milliseconds.
*/
@NonNull
public Builder setRewriteDocumentTypesLatencyMillis(int rewriteDocumentTypesLatencyMillis) {
mRewriteDocumentTypesLatencyMillis = rewriteDocumentTypesLatencyMillis;
return this;
}
/** Sets the native latency, in milliseconds. */
@NonNull
public Builder setNativeLatencyMillis(int nativeLatencyMillis) {
mNativeLatencyMillis = nativeLatencyMillis;
return this;
}
/** Sets how much time we spend on document store, in milliseconds. */
@NonNull
public Builder setNativeDocumentStoreLatencyMillis(int nativeDocumentStoreLatencyMillis) {
mNativeDocumentStoreLatencyMillis = nativeDocumentStoreLatencyMillis;
return this;
}
/** Sets the native index latency, in milliseconds. */
@NonNull
public Builder setNativeIndexLatencyMillis(int nativeIndexLatencyMillis) {
mNativeIndexLatencyMillis = nativeIndexLatencyMillis;
return this;
}
/** Sets how much time we spend on merging indices, in milliseconds. */
@NonNull
public Builder setNativeIndexMergeLatencyMillis(int nativeIndexMergeLatencyMillis) {
mNativeIndexMergeLatencyMillis = nativeIndexMergeLatencyMillis;
return this;
}
/** Sets document size, in bytes. */
@NonNull
public Builder setNativeDocumentSizeBytes(int nativeDocumentSizeBytes) {
mNativeDocumentSizeBytes = nativeDocumentSizeBytes;
return this;
}
/** Sets number of tokens indexed in native. */
@NonNull
public Builder setNativeNumTokensIndexed(int nativeNumTokensIndexed) {
mNativeNumTokensIndexed = nativeNumTokensIndexed;
return this;
}
/**
* Sets whether the number of tokens to be indexed exceeded the max number of tokens per
* document.
*/
@NonNull
public Builder setNativeExceededMaxNumTokens(boolean nativeExceededMaxNumTokens) {
mNativeExceededMaxNumTokens = nativeExceededMaxNumTokens;
return this;
}
/**
* Creates a new {@link PutDocumentStats} object from the contents of this {@link Builder}
* instance.
*/
@NonNull
public PutDocumentStats build() {
return new PutDocumentStats(/* builder= */ this);
}
}
}

View File

@@ -1,181 +0,0 @@
/*
* Copyright 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.external.localstorage.stats;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.RemoveByDocumentIdRequest;
import android.app.appsearch.SearchSpec;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/**
* Class holds detailed stats for {@link
* android.app.appsearch.AppSearchSession#remove(RemoveByDocumentIdRequest)} and {@link
* android.app.appsearch.AppSearchSession#remove(String, SearchSpec)}
*
* @hide
*/
public final class RemoveStats {
@IntDef(
value = {
// It needs to be sync with DeleteType.Code in
// external/icing/proto/icing/proto/logging.proto#DeleteStatsProto
UNKNOWN,
SINGLE,
QUERY,
NAMESPACE,
SCHEMA_TYPE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DeleteType {}
/** Default. Should never be used. */
public static final int UNKNOWN = 0;
/** Delete by namespace + id. */
public static final int SINGLE = 1;
/** Delete by query. */
public static final int QUERY = 2;
/** Delete by namespace. */
public static final int NAMESPACE = 3;
/** Delete by schema type. */
public static final int SCHEMA_TYPE = 4;
@NonNull private final String mPackageName;
@NonNull private final String mDatabase;
/**
* The status code returned by {@link AppSearchResult#getResultCode()} for the call or internal
* state.
*/
@AppSearchResult.ResultCode private final int mStatusCode;
private final int mTotalLatencyMillis;
private final int mNativeLatencyMillis;
@DeleteType private final int mNativeDeleteType;
private final int mNativeNumDocumentsDeleted;
RemoveStats(@NonNull Builder builder) {
Objects.requireNonNull(builder);
mPackageName = builder.mPackageName;
mDatabase = builder.mDatabase;
mStatusCode = builder.mStatusCode;
mTotalLatencyMillis = builder.mTotalLatencyMillis;
mNativeLatencyMillis = builder.mNativeLatencyMillis;
mNativeDeleteType = builder.mNativeDeleteType;
mNativeNumDocumentsDeleted = builder.mNativeNumDocumentsDeleted;
}
/** Returns calling package name. */
@NonNull
public String getPackageName() {
return mPackageName;
}
/** Returns calling database name. */
@NonNull
public String getDatabase() {
return mDatabase;
}
/** Returns status code for this remove. */
@AppSearchResult.ResultCode
public int getStatusCode() {
return mStatusCode;
}
/** Returns total latency of this remove in millis. */
public int getTotalLatencyMillis() {
return mTotalLatencyMillis;
}
/** Returns how much time in millis spent in the native code. */
public int getNativeLatencyMillis() {
return mNativeLatencyMillis;
}
/** Returns what type of delete for this remove call. */
@DeleteType
public int getDeleteType() {
return mNativeDeleteType;
}
/** Returns how many documents get deleted in this call. */
public int getDeletedDocumentCount() {
return mNativeNumDocumentsDeleted;
}
/** Builder for {@link RemoveStats}. */
public static class Builder {
@NonNull final String mPackageName;
@NonNull final String mDatabase;
@AppSearchResult.ResultCode int mStatusCode;
int mTotalLatencyMillis;
int mNativeLatencyMillis;
@DeleteType int mNativeDeleteType;
int mNativeNumDocumentsDeleted;
/** Constructor for the {@link Builder}. */
public Builder(@NonNull String packageName, @NonNull String database) {
mPackageName = Objects.requireNonNull(packageName);
mDatabase = Objects.requireNonNull(database);
}
/** Sets the status code. */
@NonNull
public Builder setStatusCode(@AppSearchResult.ResultCode int statusCode) {
mStatusCode = statusCode;
return this;
}
/** Sets total latency in millis. */
@NonNull
public Builder setTotalLatencyMillis(int totalLatencyMillis) {
mTotalLatencyMillis = totalLatencyMillis;
return this;
}
/** Sets native latency in millis. */
@NonNull
public Builder setNativeLatencyMillis(int nativeLatencyMillis) {
mNativeLatencyMillis = nativeLatencyMillis;
return this;
}
/** Sets delete type for this call. */
@NonNull
public Builder setDeleteType(@DeleteType int nativeDeleteType) {
mNativeDeleteType = nativeDeleteType;
return this;
}
/** Sets how many documents get deleted for this call. */
@NonNull
public Builder setDeletedDocumentCount(int nativeNumDocumentsDeleted) {
mNativeNumDocumentsDeleted = nativeNumDocumentsDeleted;
return this;
}
/** Creates a {@link RemoveStats}. */
@NonNull
public RemoveStats build() {
return new RemoveStats(/* builder= */ this);
}
}
}

View File

@@ -1,189 +0,0 @@
/*
* Copyright 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.external.localstorage.stats;
import android.annotation.NonNull;
import android.app.appsearch.SetSchemaRequest;
import java.util.Objects;
/**
* Class holds detailed stats for Schema migration.
*
* @hide
*/
// TODO(b/173532925): Hides getter and setter functions for accessing {@code
// mFirstSetSchemaLatencyMillis} and {@code mSecondSetSchemaLatencyMillis} field.
public final class SchemaMigrationStats {
/** GetSchema latency in milliseconds. */
private final int mGetSchemaLatencyMillis;
/**
* Latency of querying all documents that need to be migrated to new version and transforming
* documents to new version in milliseconds.
*/
private final int mQueryAndTransformLatencyMillis;
private final int mFirstSetSchemaLatencyMillis;
private final int mSecondSetSchemaLatencyMillis;
/** Latency of putting migrated document to Icing lib in milliseconds. */
private final int mSaveDocumentLatencyMillis;
private final int mMigratedDocumentCount;
private final int mSavedDocumentCount;
SchemaMigrationStats(@NonNull Builder builder) {
Objects.requireNonNull(builder);
mGetSchemaLatencyMillis = builder.mGetSchemaLatencyMillis;
mQueryAndTransformLatencyMillis = builder.mQueryAndTransformLatencyMillis;
mFirstSetSchemaLatencyMillis = builder.mFirstSetSchemaLatencyMillis;
mSecondSetSchemaLatencyMillis = builder.mSecondSetSchemaLatencyMillis;
mSaveDocumentLatencyMillis = builder.mSaveDocumentLatencyMillis;
mMigratedDocumentCount = builder.mMigratedDocumentCount;
mSavedDocumentCount = builder.mSavedDocumentCount;
}
/** Returns GetSchema latency in milliseconds. */
public int getGetSchemaLatencyMillis() {
return mGetSchemaLatencyMillis;
}
/**
* Returns latency of querying all documents that need to be migrated to new version and
* transforming documents to new version in milliseconds.
*/
public int getQueryAndTransformLatencyMillis() {
return mQueryAndTransformLatencyMillis;
}
/**
* Returns latency of first SetSchema action in milliseconds.
*
* <p>If all schema fields are backward compatible, the schema will be successful set to Icing.
* Otherwise, we will retrieve incompatible types here.
*
* <p>Please see {@link SetSchemaRequest} for what is "incompatible".
*/
public int getFirstSetSchemaLatencyMillis() {
return mFirstSetSchemaLatencyMillis;
}
/**
* Returns latency of second SetSchema action in milliseconds.
*
* <p>If all schema fields are backward compatible, the schema will be successful set to Icing
* in the first setSchema action and this value will be 0. Otherwise, schema types will be set
* to Icing by this action.
*/
public int getSecondSetSchemaLatencyMillis() {
return mSecondSetSchemaLatencyMillis;
}
/** Returns latency of putting migrated document to Icing lib in milliseconds. */
public int getSaveDocumentLatencyMillis() {
return mSaveDocumentLatencyMillis;
}
/** Returns number of migrated documents. */
public int getMigratedDocumentCount() {
return mMigratedDocumentCount;
}
/** Returns number of updated documents which are saved in Icing lib. */
public int getSavedDocumentCount() {
return mSavedDocumentCount;
}
/** Builder for {@link SchemaMigrationStats}. */
public static class Builder {
int mGetSchemaLatencyMillis;
int mQueryAndTransformLatencyMillis;
int mFirstSetSchemaLatencyMillis;
int mSecondSetSchemaLatencyMillis;
int mSaveDocumentLatencyMillis;
int mMigratedDocumentCount;
int mSavedDocumentCount;
/** Sets latency for the GetSchema action in milliseconds. */
@NonNull
public SchemaMigrationStats.Builder setGetSchemaLatencyMillis(int getSchemaLatencyMillis) {
mGetSchemaLatencyMillis = getSchemaLatencyMillis;
return this;
}
/**
* Sets latency for querying all documents that need to be migrated to new version and
* transforming documents to new version in milliseconds.
*/
@NonNull
public SchemaMigrationStats.Builder setQueryAndTransformLatencyMillis(
int queryAndTransformLatencyMillis) {
mQueryAndTransformLatencyMillis = queryAndTransformLatencyMillis;
return this;
}
/** Sets latency of first SetSchema action in milliseconds. */
@NonNull
public SchemaMigrationStats.Builder setFirstSetSchemaLatencyMillis(
int firstSetSchemaLatencyMillis) {
mFirstSetSchemaLatencyMillis = firstSetSchemaLatencyMillis;
return this;
}
/** Sets latency of second SetSchema action in milliseconds. */
@NonNull
public SchemaMigrationStats.Builder setSecondSetSchemaLatencyMillis(
int secondSetSchemaLatencyMillis) {
mSecondSetSchemaLatencyMillis = secondSetSchemaLatencyMillis;
return this;
}
/** Sets latency for putting migrated document to Icing lib in milliseconds. */
@NonNull
public SchemaMigrationStats.Builder setSaveDocumentLatencyMillis(
int saveDocumentLatencyMillis) {
mSaveDocumentLatencyMillis = saveDocumentLatencyMillis;
return this;
}
/** Sets number of migrated documents. */
@NonNull
public SchemaMigrationStats.Builder setMigratedDocumentCount(int migratedDocumentCount) {
mMigratedDocumentCount = migratedDocumentCount;
return this;
}
/** Sets number of updated documents which are saved in Icing lib. */
@NonNull
public SchemaMigrationStats.Builder setSavedDocumentCount(int savedDocumentCount) {
mSavedDocumentCount = savedDocumentCount;
return this;
}
/**
* Builds a new {@link SchemaMigrationStats} from the {@link SchemaMigrationStats.Builder}.
*/
@NonNull
public SchemaMigrationStats build() {
return new SchemaMigrationStats(/* builder= */ this);
}
}
}

View File

@@ -1,463 +0,0 @@
/*
* Copyright 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.external.localstorage.stats;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.SearchSpec;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/**
* Class holds detailed stats for {@link android.app.appsearch.AppSearchSession#search(String,
* SearchSpec)}
*
* @hide
*/
public final class SearchStats {
@IntDef(
value = {
// Searches apps' own documents.
VISIBILITY_SCOPE_LOCAL,
// Searches the global documents. Including platform surfaceable and 3p-access.
VISIBILITY_SCOPE_GLOBAL,
// TODO(b/173532925) Add THIRD_PARTY_ACCESS once we can distinguish platform
// surfaceable from 3p access(right both of them are categorized as
// VISIBILITY_SCOPE_GLOBAL)
})
@Retention(RetentionPolicy.SOURCE)
public @interface VisibilityScope {}
// Searches apps' own documents.
public static final int VISIBILITY_SCOPE_LOCAL = 1;
// Searches the global documents. Including platform surfaceable and 3p-access.
public static final int VISIBILITY_SCOPE_GLOBAL = 2;
// TODO(b/173532925): Add a field searchType to indicate where the search is used(normal
// query vs in removeByQuery vs during migration)
@NonNull private final String mPackageName;
@Nullable private final String mDatabase;
/**
* The status code returned by {@link AppSearchResult#getResultCode()} for the call or internal
* state.
*/
@AppSearchResult.ResultCode private final int mStatusCode;
private final int mTotalLatencyMillis;
/** Time used to rewrite the search spec. */
private final int mRewriteSearchSpecLatencyMillis;
/** Time used to rewrite the search results. */
private final int mRewriteSearchResultLatencyMillis;
/** Defines the scope the query is searching over */
@VisibilityScope private final int mVisibilityScope;
/** Overall time used for the native function call. */
private final int mNativeLatencyMillis;
/** Number of terms in the query string. */
private final int mNativeNumTerms;
/** Length of the query string. */
private final int mNativeQueryLength;
/** Number of namespaces filtered. */
private final int mNativeNumNamespacesFiltered;
/** Number of schema types filtered. */
private final int mNativeNumSchemaTypesFiltered;
/** The requested number of results in one page. */
private final int mNativeRequestedPageSize;
/** The actual number of results returned in the current page. */
private final int mNativeNumResultsReturnedCurrentPage;
/**
* Whether the function call is querying the first page. If it's not, Icing will fetch the
* results from cache so that some steps may be skipped.
*/
private final boolean mNativeIsFirstPage;
/**
* Time used to parse the query, including 2 parts: tokenizing and transforming tokens into an
* iterator tree.
*/
private final int mNativeParseQueryLatencyMillis;
/** Strategy of scoring and ranking. */
@SearchSpec.RankingStrategy private final int mNativeRankingStrategy;
/** Number of documents scored. */
private final int mNativeNumDocumentsScored;
/** Time used to score the raw results. */
private final int mNativeScoringLatencyMillis;
/** Time used to rank the scored results. */
private final int mNativeRankingLatencyMillis;
/**
* Time used to fetch the document protos. Note that it includes the time to snippet if {@link
* SearchStats#mNativeNumResultsWithSnippets} is greater than 0.
*/
private final int mNativeDocumentRetrievingLatencyMillis;
/** How many snippets are calculated. */
private final int mNativeNumResultsWithSnippets;
SearchStats(@NonNull Builder builder) {
Objects.requireNonNull(builder);
mPackageName = builder.mPackageName;
mDatabase = builder.mDatabase;
mStatusCode = builder.mStatusCode;
mTotalLatencyMillis = builder.mTotalLatencyMillis;
mRewriteSearchSpecLatencyMillis = builder.mRewriteSearchSpecLatencyMillis;
mRewriteSearchResultLatencyMillis = builder.mRewriteSearchResultLatencyMillis;
mVisibilityScope = builder.mVisibilityScope;
mNativeLatencyMillis = builder.mNativeLatencyMillis;
mNativeNumTerms = builder.mNativeNumTerms;
mNativeQueryLength = builder.mNativeQueryLength;
mNativeNumNamespacesFiltered = builder.mNativeNumNamespacesFiltered;
mNativeNumSchemaTypesFiltered = builder.mNativeNumSchemaTypesFiltered;
mNativeRequestedPageSize = builder.mNativeRequestedPageSize;
mNativeNumResultsReturnedCurrentPage = builder.mNativeNumResultsReturnedCurrentPage;
mNativeIsFirstPage = builder.mNativeIsFirstPage;
mNativeParseQueryLatencyMillis = builder.mNativeParseQueryLatencyMillis;
mNativeRankingStrategy = builder.mNativeRankingStrategy;
mNativeNumDocumentsScored = builder.mNativeNumDocumentsScored;
mNativeScoringLatencyMillis = builder.mNativeScoringLatencyMillis;
mNativeRankingLatencyMillis = builder.mNativeRankingLatencyMillis;
mNativeNumResultsWithSnippets = builder.mNativeNumResultsWithSnippets;
mNativeDocumentRetrievingLatencyMillis = builder.mNativeDocumentRetrievingLatencyMillis;
}
/** Returns the package name of the session. */
@NonNull
public String getPackageName() {
return mPackageName;
}
/**
* Returns the database name of the session.
*
* @return database name used by the session. {@code null} if and only if it is a global
* search(visibilityScope is {@link SearchStats#VISIBILITY_SCOPE_GLOBAL}).
*/
@Nullable
public String getDatabase() {
return mDatabase;
}
/** Returns status of the search. */
@AppSearchResult.ResultCode
public int getStatusCode() {
return mStatusCode;
}
/** Returns the total latency of the search. */
public int getTotalLatencyMillis() {
return mTotalLatencyMillis;
}
/** Returns how much time spent on rewriting the {@link SearchSpec}. */
public int getRewriteSearchSpecLatencyMillis() {
return mRewriteSearchSpecLatencyMillis;
}
/** Returns how much time spent on rewriting the {@link android.app.appsearch.SearchResult}. */
public int getRewriteSearchResultLatencyMillis() {
return mRewriteSearchResultLatencyMillis;
}
/** Returns the visibility scope of the search. */
@VisibilityScope
public int getVisibilityScope() {
return mVisibilityScope;
}
/** Returns how much time spent on the native calls. */
public int getNativeLatencyMillis() {
return mNativeLatencyMillis;
}
/** Returns number of terms in the search string. */
public int getTermCount() {
return mNativeNumTerms;
}
/** Returns the length of the search string. */
public int getQueryLength() {
return mNativeQueryLength;
}
/** Returns number of namespaces filtered. */
public int getFilteredNamespaceCount() {
return mNativeNumNamespacesFiltered;
}
/** Returns number of schema types filtered. */
public int getFilteredSchemaTypeCount() {
return mNativeNumSchemaTypesFiltered;
}
/** Returns the requested number of results in one page. */
public int getRequestedPageSize() {
return mNativeRequestedPageSize;
}
/** Returns the actual number of results returned in the current page. */
public int getCurrentPageReturnedResultCount() {
return mNativeNumResultsReturnedCurrentPage;
}
// TODO(b/185184738) Make it an integer to show how many pages having been returned.
/** Returns whether the function call is querying the first page. */
public boolean isFirstPage() {
return mNativeIsFirstPage;
}
/**
* Returns time used to parse the query, including 2 parts: tokenizing and transforming tokens
* into an iterator tree.
*/
public int getParseQueryLatencyMillis() {
return mNativeParseQueryLatencyMillis;
}
/** Returns strategy of scoring and ranking. */
@SearchSpec.RankingStrategy
public int getRankingStrategy() {
return mNativeRankingStrategy;
}
/** Returns number of documents scored. */
public int getScoredDocumentCount() {
return mNativeNumDocumentsScored;
}
/** Returns time used to score the raw results. */
public int getScoringLatencyMillis() {
return mNativeScoringLatencyMillis;
}
/** Returns time used to rank the scored results. */
public int getRankingLatencyMillis() {
return mNativeRankingLatencyMillis;
}
/**
* Returns time used to fetch the document protos. Note that it includes the time to snippet if
* {@link SearchStats#mNativeNumResultsWithSnippets} is not zero.
*/
public int getDocumentRetrievingLatencyMillis() {
return mNativeDocumentRetrievingLatencyMillis;
}
/** Returns the number of the results in the page returned were snippeted. */
public int getResultWithSnippetsCount() {
return mNativeNumResultsWithSnippets;
}
/** Builder for {@link SearchStats} */
public static class Builder {
@NonNull final String mPackageName;
@Nullable String mDatabase;
@AppSearchResult.ResultCode int mStatusCode;
int mTotalLatencyMillis;
int mRewriteSearchSpecLatencyMillis;
int mRewriteSearchResultLatencyMillis;
int mVisibilityScope;
int mNativeLatencyMillis;
int mNativeNumTerms;
int mNativeQueryLength;
int mNativeNumNamespacesFiltered;
int mNativeNumSchemaTypesFiltered;
int mNativeRequestedPageSize;
int mNativeNumResultsReturnedCurrentPage;
boolean mNativeIsFirstPage;
int mNativeParseQueryLatencyMillis;
int mNativeRankingStrategy;
int mNativeNumDocumentsScored;
int mNativeScoringLatencyMillis;
int mNativeRankingLatencyMillis;
int mNativeNumResultsWithSnippets;
int mNativeDocumentRetrievingLatencyMillis;
/**
* Constructor
*
* @param visibilityScope scope for the corresponding search.
* @param packageName name of the calling package.
*/
public Builder(@VisibilityScope int visibilityScope, @NonNull String packageName) {
mVisibilityScope = visibilityScope;
mPackageName = Objects.requireNonNull(packageName);
}
/** Sets the database used by the session. */
@NonNull
public Builder setDatabase(@NonNull String database) {
mDatabase = Objects.requireNonNull(database);
return this;
}
/** Sets the status of the search. */
@NonNull
public Builder setStatusCode(@AppSearchResult.ResultCode int statusCode) {
mStatusCode = statusCode;
return this;
}
/** Sets total latency for the search. */
@NonNull
public Builder setTotalLatencyMillis(int totalLatencyMillis) {
mTotalLatencyMillis = totalLatencyMillis;
return this;
}
/** Sets time used to rewrite the search spec. */
@NonNull
public Builder setRewriteSearchSpecLatencyMillis(int rewriteSearchSpecLatencyMillis) {
mRewriteSearchSpecLatencyMillis = rewriteSearchSpecLatencyMillis;
return this;
}
/** Sets time used to rewrite the search results. */
@NonNull
public Builder setRewriteSearchResultLatencyMillis(int rewriteSearchResultLatencyMillis) {
mRewriteSearchResultLatencyMillis = rewriteSearchResultLatencyMillis;
return this;
}
/** Sets overall time used for the native function calls. */
@NonNull
public Builder setNativeLatencyMillis(int nativeLatencyMillis) {
mNativeLatencyMillis = nativeLatencyMillis;
return this;
}
/** Sets number of terms in the search string. */
@NonNull
public Builder setTermCount(int termCount) {
mNativeNumTerms = termCount;
return this;
}
/** Sets length of the search string. */
@NonNull
public Builder setQueryLength(int queryLength) {
mNativeQueryLength = queryLength;
return this;
}
/** Sets number of namespaces filtered. */
@NonNull
public Builder setFilteredNamespaceCount(int filteredNamespaceCount) {
mNativeNumNamespacesFiltered = filteredNamespaceCount;
return this;
}
/** Sets number of schema types filtered. */
@NonNull
public Builder setFilteredSchemaTypeCount(int filteredSchemaTypeCount) {
mNativeNumSchemaTypesFiltered = filteredSchemaTypeCount;
return this;
}
/** Sets the requested number of results in one page. */
@NonNull
public Builder setRequestedPageSize(int requestedPageSize) {
mNativeRequestedPageSize = requestedPageSize;
return this;
}
/** Sets the actual number of results returned in the current page. */
@NonNull
public Builder setCurrentPageReturnedResultCount(int currentPageReturnedResultCount) {
mNativeNumResultsReturnedCurrentPage = currentPageReturnedResultCount;
return this;
}
/**
* Sets whether the function call is querying the first page. If it's not, Icing will fetch
* the results from cache so that some steps may be skipped.
*/
@NonNull
public Builder setIsFirstPage(boolean nativeIsFirstPage) {
mNativeIsFirstPage = nativeIsFirstPage;
return this;
}
/**
* Sets time used to parse the query, including 2 parts: tokenizing and transforming tokens
* into an iterator tree.
*/
@NonNull
public Builder setParseQueryLatencyMillis(int parseQueryLatencyMillis) {
mNativeParseQueryLatencyMillis = parseQueryLatencyMillis;
return this;
}
/** Sets strategy of scoring and ranking. */
@NonNull
public Builder setRankingStrategy(@SearchSpec.RankingStrategy int rankingStrategy) {
mNativeRankingStrategy = rankingStrategy;
return this;
}
/** Sets number of documents scored. */
@NonNull
public Builder setScoredDocumentCount(int scoredDocumentCount) {
mNativeNumDocumentsScored = scoredDocumentCount;
return this;
}
/** Sets time used to score the raw results. */
@NonNull
public Builder setScoringLatencyMillis(int scoringLatencyMillis) {
mNativeScoringLatencyMillis = scoringLatencyMillis;
return this;
}
/** Sets time used to rank the scored results. */
@NonNull
public Builder setRankingLatencyMillis(int rankingLatencyMillis) {
mNativeRankingLatencyMillis = rankingLatencyMillis;
return this;
}
/** Sets time used to fetch the document protos. */
@NonNull
public Builder setDocumentRetrievingLatencyMillis(int documentRetrievingLatencyMillis) {
mNativeDocumentRetrievingLatencyMillis = documentRetrievingLatencyMillis;
return this;
}
/** Sets how many snippets are calculated. */
@NonNull
public Builder setResultWithSnippetsCount(int resultWithSnippetsCount) {
mNativeNumResultsWithSnippets = resultWithSnippetsCount;
return this;
}
/**
* Constructs a new {@link SearchStats} from the contents of this {@link
* SearchStats.Builder}.
*/
@NonNull
public SearchStats build() {
if (mDatabase == null) {
Preconditions.checkState(
mVisibilityScope != SearchStats.VISIBILITY_SCOPE_LOCAL,
"database can not be null if visibilityScope is local.");
}
return new SearchStats(/* builder= */ this);
}
}
}

View File

@@ -1,245 +0,0 @@
/*
* Copyright 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.external.localstorage.stats;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.appsearch.AppSearchResult;
import java.util.Objects;
/**
* Class holds detailed stats for {@link
* android.app.appsearch.AppSearchSession#setSchema(SetSchemaRequest)}.
*
* @hide
*/
public final class SetSchemaStats {
@NonNull private final String mPackageName;
@NonNull private final String mDatabase;
/**
* The status code returned by {@link AppSearchResult#getResultCode()} for the call or internal
* state.
*/
@AppSearchResult.ResultCode private final int mStatusCode;
/**
* Stores stats of SchemaMigration in SetSchema process. Is {@code null} if no schema migration
* is needed.
*/
@Nullable private final SchemaMigrationStats mSchemaMigrationStats;
private final int mTotalLatencyMillis;
/** Overall time used for the native function call. */
private final int mNativeLatencyMillis;
/** Number of newly added schema types. */
private final int mNewTypeCount;
/** Number of deleted schema types. */
private final int mDeletedTypeCount;
/** Number of compatible schema type changes. */
private final int mCompatibleTypeChangeCount;
/** Number of index-incompatible schema type changes. */
private final int mIndexIncompatibleTypeChangeCount;
/** Number of backwards-incompatible schema type changes. */
private final int mBackwardsIncompatibleTypeChangeCount;
SetSchemaStats(@NonNull Builder builder) {
Objects.requireNonNull(builder);
mPackageName = builder.mPackageName;
mDatabase = builder.mDatabase;
mStatusCode = builder.mStatusCode;
mSchemaMigrationStats = builder.mSchemaMigrationStats;
mTotalLatencyMillis = builder.mTotalLatencyMillis;
mNativeLatencyMillis = builder.mNativeLatencyMillis;
mNewTypeCount = builder.mNewTypeCount;
mDeletedTypeCount = builder.mDeletedTypeCount;
mCompatibleTypeChangeCount = builder.mCompatibleTypeChangeCount;
mIndexIncompatibleTypeChangeCount = builder.mIndexIncompatibleTypeChangeCount;
mBackwardsIncompatibleTypeChangeCount = builder.mBackwardsIncompatibleTypeChangeCount;
}
/** Returns calling package name. */
@NonNull
public String getPackageName() {
return mPackageName;
}
/** Returns calling database name. */
@NonNull
public String getDatabase() {
return mDatabase;
}
/** Returns status of the SetSchema action. */
@AppSearchResult.ResultCode
public int getStatusCode() {
return mStatusCode;
}
/**
* Returns the status of schema migration, if migration is executed during the SetSchema
* process. Otherwise, returns {@code null}.
*/
@Nullable
public SchemaMigrationStats getSchemaMigrationStats() {
return mSchemaMigrationStats;
}
/** Returns the total latency of the SetSchema action. */
public int getTotalLatencyMillis() {
return mTotalLatencyMillis;
}
/** Returns overall time used for the native function call. */
public int getNativeLatencyMillis() {
return mNativeLatencyMillis;
}
/** Returns number of newly added schema types. */
public int getNewTypeCount() {
return mNewTypeCount;
}
/** Returns number of deleted schema types. */
public int getDeletedTypeCount() {
return mDeletedTypeCount;
}
/** Returns number of compatible type changes. */
public int getCompatibleTypeChangeCount() {
return mCompatibleTypeChangeCount;
}
/**
* Returns number of index-incompatible type change.
*
* <p>An index-incompatible type change is one that affects how pre-existing data should be
* searched over, such as modifying the {@code IndexingType} of an existing property.
*/
public int getIndexIncompatibleTypeChangeCount() {
return mIndexIncompatibleTypeChangeCount;
}
/**
* Returns number of backwards-incompatible type change.
*
* <p>For details on what constitutes a backward-incompatible type change, please see {@link
* android.app.appsearch.SetSchemaRequest}.
*/
public int getBackwardsIncompatibleTypeChangeCount() {
return mBackwardsIncompatibleTypeChangeCount;
}
/** Builder for {@link SetSchemaStats}. */
public static class Builder {
@NonNull final String mPackageName;
@NonNull final String mDatabase;
@AppSearchResult.ResultCode int mStatusCode;
@Nullable SchemaMigrationStats mSchemaMigrationStats;
int mTotalLatencyMillis;
int mNativeLatencyMillis;
int mNewTypeCount;
int mDeletedTypeCount;
int mCompatibleTypeChangeCount;
int mIndexIncompatibleTypeChangeCount;
int mBackwardsIncompatibleTypeChangeCount;
/** Constructor for the {@link Builder}. */
public Builder(@NonNull String packageName, @NonNull String database) {
mPackageName = Objects.requireNonNull(packageName);
mDatabase = Objects.requireNonNull(database);
}
/** Sets the status of the SetSchema action. */
@NonNull
public Builder setStatusCode(@AppSearchResult.ResultCode int statusCode) {
mStatusCode = statusCode;
return this;
}
/** Sets the status of schema migration. */
@NonNull
public Builder setSchemaMigrationStats(@NonNull SchemaMigrationStats schemaMigrationStats) {
mSchemaMigrationStats = Objects.requireNonNull(schemaMigrationStats);
return this;
}
/** Sets total latency for the SetSchema action in milliseconds. */
@NonNull
public Builder setTotalLatencyMillis(int totalLatencyMillis) {
mTotalLatencyMillis = totalLatencyMillis;
return this;
}
/** Sets native latency in milliseconds. */
@NonNull
public Builder setNativeLatencyMillis(int nativeLatencyMillis) {
mNativeLatencyMillis = nativeLatencyMillis;
return this;
}
/** Sets number of new types. */
@NonNull
public Builder setNewTypeCount(int newTypeCount) {
mNewTypeCount = newTypeCount;
return this;
}
/** Sets number of deleted types. */
@NonNull
public Builder setDeletedTypeCount(int deletedTypeCount) {
mDeletedTypeCount = deletedTypeCount;
return this;
}
/** Sets number of compatible type changes. */
@NonNull
public Builder setCompatibleTypeChangeCount(int compatibleTypeChangeCount) {
mCompatibleTypeChangeCount = compatibleTypeChangeCount;
return this;
}
/** Sets number of index-incompatible type changes. */
@NonNull
public Builder setIndexIncompatibleTypeChangeCount(int indexIncompatibleTypeChangeCount) {
mIndexIncompatibleTypeChangeCount = indexIncompatibleTypeChangeCount;
return this;
}
/** Sets number of backwards-incompatible type changes. */
@NonNull
public Builder setBackwardsIncompatibleTypeChangeCount(
int backwardsIncompatibleTypeChangeCount) {
mBackwardsIncompatibleTypeChangeCount = backwardsIncompatibleTypeChangeCount;
return this;
}
/** Builds a new {@link SetSchemaStats} from the {@link Builder}. */
@NonNull
public SetSchemaStats build() {
return new SetSchemaStats(/* builder= */ this);
}
}
}

View File

@@ -1,235 +0,0 @@
/*
* Copyright 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.external.localstorage.util;
import android.annotation.NonNull;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.exceptions.AppSearchException;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.google.android.icing.proto.DocumentProto;
import com.google.android.icing.proto.PropertyProto;
/**
* Provides utility functions for working with package + database prefixes.
*
* @hide
*/
public class PrefixUtil {
private static final String TAG = "AppSearchPrefixUtil";
@VisibleForTesting public static final char DATABASE_DELIMITER = '/';
@VisibleForTesting public static final char PACKAGE_DELIMITER = '$';
private PrefixUtil() {}
/** Creates prefix string for given package name and database name. */
@NonNull
public static String createPrefix(@NonNull String packageName, @NonNull String databaseName) {
return packageName + PACKAGE_DELIMITER + databaseName + DATABASE_DELIMITER;
}
/** Creates prefix string for given package name. */
@NonNull
public static String createPackagePrefix(@NonNull String packageName) {
return packageName + PACKAGE_DELIMITER;
}
/**
* Returns the package name that's contained within the {@code prefix}.
*
* @param prefix Prefix string that contains the package name inside of it. The package name
* must be in the front of the string, and separated from the rest of the string by the
* {@link #PACKAGE_DELIMITER}.
* @return Valid package name.
*/
@NonNull
public static String getPackageName(@NonNull String prefix) {
int delimiterIndex = prefix.indexOf(PACKAGE_DELIMITER);
if (delimiterIndex == -1) {
// This should never happen if we construct our prefixes properly
Log.wtf(TAG, "Malformed prefix doesn't contain package delimiter: " + prefix);
return "";
}
return prefix.substring(0, delimiterIndex);
}
/**
* Returns the database name that's contained within the {@code prefix}.
*
* @param prefix Prefix string that contains the database name inside of it. The database name
* must be between the {@link #PACKAGE_DELIMITER} and {@link #DATABASE_DELIMITER}
* @return Valid database name.
*/
@NonNull
public static String getDatabaseName(@NonNull String prefix) {
// TODO (b/184050178) Start database delimiter index search from after package delimiter
int packageDelimiterIndex = prefix.indexOf(PACKAGE_DELIMITER);
int databaseDelimiterIndex = prefix.indexOf(DATABASE_DELIMITER);
if (packageDelimiterIndex == -1) {
// This should never happen if we construct our prefixes properly
Log.wtf(TAG, "Malformed prefix doesn't contain package delimiter: " + prefix);
return "";
}
if (databaseDelimiterIndex == -1) {
// This should never happen if we construct our prefixes properly
Log.wtf(TAG, "Malformed prefix doesn't contain database delimiter: " + prefix);
return "";
}
return prefix.substring(packageDelimiterIndex + 1, databaseDelimiterIndex);
}
/**
* Creates a string with the package and database prefix removed from the input string.
*
* @param prefixedString a string containing a package and database prefix.
* @return a string with the package and database prefix removed.
* @throws AppSearchException if the prefixed value does not contain a valid database name.
*/
@NonNull
public static String removePrefix(@NonNull String prefixedString) throws AppSearchException {
// The prefix is made up of the package, then the database. So we only need to find the
// database cutoff.
int delimiterIndex;
if ((delimiterIndex = prefixedString.indexOf(DATABASE_DELIMITER)) != -1) {
// Add 1 to include the char size of the DATABASE_DELIMITER
return prefixedString.substring(delimiterIndex + 1);
}
throw new AppSearchException(
AppSearchResult.RESULT_INTERNAL_ERROR,
"The prefixed value \""
+ prefixedString
+ "\" doesn't contain a valid "
+ "database name");
}
/**
* Creates a package and database prefix string from the input string.
*
* @param prefixedString a string containing a package and database prefix.
* @return a string with the package and database prefix
* @throws AppSearchException if the prefixed value does not contain a valid database name.
*/
@NonNull
public static String getPrefix(@NonNull String prefixedString) throws AppSearchException {
int databaseDelimiterIndex = prefixedString.indexOf(DATABASE_DELIMITER);
if (databaseDelimiterIndex == -1) {
throw new AppSearchException(
AppSearchResult.RESULT_INTERNAL_ERROR,
"The prefixed value \""
+ prefixedString
+ "\" doesn't contain a valid "
+ "database name");
}
// Add 1 to include the char size of the DATABASE_DELIMITER
return prefixedString.substring(0, databaseDelimiterIndex + 1);
}
/**
* Prepends {@code prefix} to all types and namespaces mentioned anywhere in {@code
* documentBuilder}.
*
* @param documentBuilder The document to mutate
* @param prefix The prefix to add
*/
public static void addPrefixToDocument(
@NonNull DocumentProto.Builder documentBuilder, @NonNull String prefix) {
// Rewrite the type name to include/remove the prefix.
String newSchema = prefix + documentBuilder.getSchema();
documentBuilder.setSchema(newSchema);
// Rewrite the namespace to include/remove the prefix.
documentBuilder.setNamespace(prefix + documentBuilder.getNamespace());
// Recurse into derived documents
for (int propertyIdx = 0;
propertyIdx < documentBuilder.getPropertiesCount();
propertyIdx++) {
int documentCount = documentBuilder.getProperties(propertyIdx).getDocumentValuesCount();
if (documentCount > 0) {
PropertyProto.Builder propertyBuilder =
documentBuilder.getProperties(propertyIdx).toBuilder();
for (int documentIdx = 0; documentIdx < documentCount; documentIdx++) {
DocumentProto.Builder derivedDocumentBuilder =
propertyBuilder.getDocumentValues(documentIdx).toBuilder();
addPrefixToDocument(derivedDocumentBuilder, prefix);
propertyBuilder.setDocumentValues(documentIdx, derivedDocumentBuilder);
}
documentBuilder.setProperties(propertyIdx, propertyBuilder);
}
}
}
/**
* Removes any prefixes from types and namespaces mentioned anywhere in {@code documentBuilder}.
*
* @param documentBuilder The document to mutate
* @return Prefix name that was removed from the document.
* @throws AppSearchException if there are unexpected database prefixing errors.
*/
@NonNull
public static String removePrefixesFromDocument(@NonNull DocumentProto.Builder documentBuilder)
throws AppSearchException {
// Rewrite the type name and namespace to remove the prefix.
String schemaPrefix = getPrefix(documentBuilder.getSchema());
String namespacePrefix = getPrefix(documentBuilder.getNamespace());
if (!schemaPrefix.equals(namespacePrefix)) {
throw new AppSearchException(
AppSearchResult.RESULT_INTERNAL_ERROR,
"Found unexpected"
+ " multiple prefix names in document: "
+ schemaPrefix
+ ", "
+ namespacePrefix);
}
documentBuilder.setSchema(removePrefix(documentBuilder.getSchema()));
documentBuilder.setNamespace(removePrefix(documentBuilder.getNamespace()));
// Recurse into derived documents
for (int propertyIdx = 0;
propertyIdx < documentBuilder.getPropertiesCount();
propertyIdx++) {
int documentCount = documentBuilder.getProperties(propertyIdx).getDocumentValuesCount();
if (documentCount > 0) {
PropertyProto.Builder propertyBuilder =
documentBuilder.getProperties(propertyIdx).toBuilder();
for (int documentIdx = 0; documentIdx < documentCount; documentIdx++) {
DocumentProto.Builder derivedDocumentBuilder =
propertyBuilder.getDocumentValues(documentIdx).toBuilder();
String nestedPrefix = removePrefixesFromDocument(derivedDocumentBuilder);
if (!nestedPrefix.equals(schemaPrefix)) {
throw new AppSearchException(
AppSearchResult.RESULT_INTERNAL_ERROR,
"Found unexpected multiple prefix names in document: "
+ schemaPrefix
+ ", "
+ nestedPrefix);
}
propertyBuilder.setDocumentValues(documentIdx, derivedDocumentBuilder);
}
documentBuilder.setProperties(propertyIdx, propertyBuilder);
}
}
return schemaPrefix;
}
}

View File

@@ -1,72 +0,0 @@
/*
* Copyright 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.external.localstorage.visibilitystore;
import android.annotation.NonNull;
import android.app.appsearch.PackageIdentifier;
import android.app.appsearch.exceptions.AppSearchException;
import com.android.internal.annotations.VisibleForTesting;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* An interface for classes that store and validate document visibility data.
*
* @hide
*/
public interface VisibilityStore {
/**
* These cannot have any of the special characters used by AppSearchImpl (e.g. {@code
* AppSearchImpl#PACKAGE_DELIMITER} or {@code AppSearchImpl#DATABASE_DELIMITER}.
*/
String PACKAGE_NAME = "VS#Pkg";
@VisibleForTesting String DATABASE_NAME = "VS#Db";
/**
* Sets visibility settings for the given database. Any previous visibility settings will be
* overwritten.
*
* @param packageName Package of app that owns the schemas.
* @param databaseName Database that owns the schemas.
* @param schemasNotDisplayedBySystem Set of prefixed schemas that should be hidden from
* platform surfaces.
* @param schemasVisibleToPackages Map of prefixed schemas to a list of package identifiers that
* have access to the schema.
* @throws AppSearchException on AppSearchImpl error.
*/
void setVisibility(
@NonNull String packageName,
@NonNull String databaseName,
@NonNull Set<String> schemasNotDisplayedBySystem,
@NonNull Map<String, List<PackageIdentifier>> schemasVisibleToPackages)
throws AppSearchException;
/**
* Checks whether the given package has access to system-surfaceable schemas.
*
* @param callerUid UID of the app that wants to see the data.
*/
boolean isSchemaSearchableByCaller(
@NonNull String packageName,
@NonNull String databaseName,
@NonNull String prefixedSchema,
int callerUid,
boolean callerHasSystemAccess);
}

View File

@@ -1,527 +0,0 @@
/*
* 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.Nullable;
import android.app.appsearch.exceptions.AppSearchException;
import android.content.Context;
import android.os.Process;
import android.os.SystemClock;
import android.util.ArrayMap;
import android.util.Log;
import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.appsearch.AppSearchConfig;
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.InitializeStats;
import com.android.server.appsearch.external.localstorage.stats.OptimizeStats;
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.util.PackageUtil;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
/**
* Logger Implementation for pushed atoms.
*
* <p>This class is thread-safe.
*
* @hide
*/
public final class PlatformLogger implements AppSearchLogger {
private static final String TAG = "AppSearchPlatformLogger";
// Context of the user we're logging for.
private final Context mUserContext;
// Manager holding the configuration flags
private final AppSearchConfig mConfig;
private final Random mRng = new Random();
private final Object mLock = new Object();
/**
* SparseArray to track how many stats we skipped due to
* {@link AppSearchConfig#getCachedMinTimeIntervalBetweenSamplesMillis()}.
*
* <p> We can have correct extrapolated number by adding those counts back when we log
* the same type of stats next time. E.g. the true count of an event could be estimated as:
* SUM(sampling_interval * (num_skipped_sample + 1)) as est_count
*
* <p>The key to the SparseArray is {@link CallStats.CallType}
*/
@GuardedBy("mLock")
private final SparseIntArray mSkippedSampleCountLocked =
new SparseIntArray();
/**
* Map to cache the packageUid for each package.
*
* <p>It maps packageName to packageUid.
*
* <p>The entry will be removed whenever the app gets uninstalled
*/
@GuardedBy("mLock")
private final Map<String, Integer> mPackageUidCacheLocked =
new ArrayMap<>();
/**
* Elapsed time for last stats logged from boot in millis
*/
@GuardedBy("mLock")
private long mLastPushTimeMillisLocked = 0;
/**
* Helper class to hold platform specific stats for statsd.
*/
static final class ExtraStats {
// UID for the calling package of the stats.
final int mPackageUid;
// sampling interval for the call type of the stats.
final int mSamplingInterval;
// number of samplings skipped before the current one for the same call type.
final int mSkippedSampleCount;
ExtraStats(int packageUid, int samplingInterval, int skippedSampleCount) {
mPackageUid = packageUid;
mSamplingInterval = samplingInterval;
mSkippedSampleCount = skippedSampleCount;
}
}
/**
* Constructor
*/
public PlatformLogger(
@NonNull Context userContext,
@NonNull AppSearchConfig config) {
mUserContext = Objects.requireNonNull(userContext);
mConfig = Objects.requireNonNull(config);
}
/** Logs {@link CallStats}. */
@Override
public void logStats(@NonNull CallStats stats) {
Objects.requireNonNull(stats);
synchronized (mLock) {
if (shouldLogForTypeLocked(stats.getCallType())) {
logStatsImplLocked(stats);
}
}
}
/** Logs {@link PutDocumentStats}. */
@Override
public void logStats(@NonNull PutDocumentStats stats) {
Objects.requireNonNull(stats);
synchronized (mLock) {
if (shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)) {
logStatsImplLocked(stats);
}
}
}
@Override
public void logStats(@NonNull InitializeStats stats) {
Objects.requireNonNull(stats);
synchronized (mLock) {
if (shouldLogForTypeLocked(CallStats.CALL_TYPE_INITIALIZE)) {
logStatsImplLocked(stats);
}
}
}
@Override
public void logStats(@NonNull SearchStats stats) {
Objects.requireNonNull(stats);
synchronized (mLock) {
if (shouldLogForTypeLocked(CallStats.CALL_TYPE_SEARCH)) {
logStatsImplLocked(stats);
}
}
}
@Override
public void logStats(@NonNull RemoveStats stats) {
// TODO(b/173532925): Log stats
}
@Override
public void logStats(@NonNull OptimizeStats stats) {
Objects.requireNonNull(stats);
synchronized (mLock) {
if (shouldLogForTypeLocked(CallStats.CALL_TYPE_OPTIMIZE)) {
logStatsImplLocked(stats);
}
}
}
/**
* Removes cached UID for package.
*
* @return removed UID for the package, or {@code INVALID_UID} if package was not previously
* cached.
*/
public int removeCachedUidForPackage(@NonNull String packageName) {
// TODO(b/173532925) This needs to be called when we get PACKAGE_REMOVED intent
Objects.requireNonNull(packageName);
synchronized (mLock) {
Integer uid = mPackageUidCacheLocked.remove(packageName);
return uid != null ? uid : Process.INVALID_UID;
}
}
@GuardedBy("mLock")
private void logStatsImplLocked(@NonNull CallStats stats) {
mLastPushTimeMillisLocked = SystemClock.elapsedRealtime();
ExtraStats extraStats = createExtraStatsLocked(stats.getPackageName(), stats.getCallType());
String database = stats.getDatabase();
try {
int hashCodeForDatabase = calculateHashCodeMd5(database);
AppSearchStatsLog.write(AppSearchStatsLog.APP_SEARCH_CALL_STATS_REPORTED,
extraStats.mSamplingInterval,
extraStats.mSkippedSampleCount,
extraStats.mPackageUid,
hashCodeForDatabase,
stats.getStatusCode(),
stats.getTotalLatencyMillis(),
stats.getCallType(),
stats.getEstimatedBinderLatencyMillis(),
stats.getNumOperationsSucceeded(),
stats.getNumOperationsFailed());
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
// TODO(b/184204720) report hashing error to statsd
// 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"
if (database != null) {
Log.e(TAG, "Error calculating hash code for database " + database, e);
}
}
}
@GuardedBy("mLock")
private void logStatsImplLocked(@NonNull PutDocumentStats stats) {
mLastPushTimeMillisLocked = SystemClock.elapsedRealtime();
ExtraStats extraStats = createExtraStatsLocked(
stats.getPackageName(), CallStats.CALL_TYPE_PUT_DOCUMENT);
String database = stats.getDatabase();
try {
int hashCodeForDatabase = calculateHashCodeMd5(database);
AppSearchStatsLog.write(AppSearchStatsLog.APP_SEARCH_PUT_DOCUMENT_STATS_REPORTED,
extraStats.mSamplingInterval,
extraStats.mSkippedSampleCount,
extraStats.mPackageUid,
hashCodeForDatabase,
stats.getStatusCode(),
stats.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 statsd
// 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"
if (database != null) {
Log.e(TAG, "Error calculating hash code for database " + database, e);
}
}
}
@GuardedBy("mLock")
private void logStatsImplLocked(@NonNull SearchStats stats) {
mLastPushTimeMillisLocked = SystemClock.elapsedRealtime();
ExtraStats extraStats = createExtraStatsLocked(stats.getPackageName(),
CallStats.CALL_TYPE_SEARCH);
String database = stats.getDatabase();
try {
int hashCodeForDatabase = calculateHashCodeMd5(database);
AppSearchStatsLog.write(AppSearchStatsLog.APP_SEARCH_QUERY_STATS_REPORTED,
extraStats.mSamplingInterval,
extraStats.mSkippedSampleCount,
extraStats.mPackageUid,
hashCodeForDatabase,
stats.getStatusCode(),
stats.getTotalLatencyMillis(),
stats.getRewriteSearchSpecLatencyMillis(),
stats.getRewriteSearchResultLatencyMillis(),
stats.getVisibilityScope(),
stats.getNativeLatencyMillis(),
stats.getTermCount(),
stats.getQueryLength(),
stats.getFilteredNamespaceCount(),
stats.getFilteredSchemaTypeCount(),
stats.getRequestedPageSize(),
stats.getCurrentPageReturnedResultCount(),
stats.isFirstPage(),
stats.getParseQueryLatencyMillis(),
stats.getRankingStrategy(),
stats.getScoredDocumentCount(),
stats.getScoringLatencyMillis(),
stats.getRankingLatencyMillis(),
stats.getDocumentRetrievingLatencyMillis(),
stats.getResultWithSnippetsCount());
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
// TODO(b/184204720) report hashing error to statsd
// 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"
if (database != null) {
Log.e(TAG, "Error calculating hash code for database " + database, e);
}
}
}
@GuardedBy("mLock")
private void logStatsImplLocked(@NonNull InitializeStats stats) {
mLastPushTimeMillisLocked = SystemClock.elapsedRealtime();
ExtraStats extraStats = createExtraStatsLocked(/*packageName=*/ null,
CallStats.CALL_TYPE_INITIALIZE);
AppSearchStatsLog.write(AppSearchStatsLog.APP_SEARCH_INITIALIZE_STATS_REPORTED,
extraStats.mSamplingInterval,
extraStats.mSkippedSampleCount,
extraStats.mPackageUid,
stats.getStatusCode(),
stats.getTotalLatencyMillis(),
stats.hasDeSync(),
stats.getPrepareSchemaAndNamespacesLatencyMillis(),
stats.getPrepareVisibilityStoreLatencyMillis(),
stats.getNativeLatencyMillis(),
stats.getDocumentStoreRecoveryCause(),
stats.getIndexRestorationCause(),
stats.getSchemaStoreRecoveryCause(),
stats.getDocumentStoreRecoveryLatencyMillis(),
stats.getIndexRestorationLatencyMillis(),
stats.getSchemaStoreRecoveryLatencyMillis(),
stats.getDocumentStoreDataStatus(),
stats.getDocumentCount(),
stats.getSchemaTypeCount(),
stats.hasReset(),
stats.getResetStatusCode());
}
@GuardedBy("mLock")
private void logStatsImplLocked(@NonNull OptimizeStats stats) {
mLastPushTimeMillisLocked = SystemClock.elapsedRealtime();
ExtraStats extraStats = createExtraStatsLocked(/*packageName=*/ null,
CallStats.CALL_TYPE_OPTIMIZE);
AppSearchStatsLog.write(AppSearchStatsLog.APP_SEARCH_OPTIMIZE_STATS_REPORTED,
extraStats.mSamplingInterval,
extraStats.mSkippedSampleCount,
stats.getStatusCode(),
stats.getTotalLatencyMillis(),
stats.getNativeLatencyMillis(),
stats.getDocumentStoreOptimizeLatencyMillis(),
stats.getIndexRestorationLatencyMillis(),
stats.getOriginalDocumentCount(),
stats.getDeletedDocumentCount(),
stats.getExpiredDocumentCount(),
stats.getStorageSizeBeforeBytes(),
stats.getStorageSizeAfterBytes(),
stats.getTimeSinceLastOptimizeMillis());
}
/**
* 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. returns -1 if str is null.
* @throws AppSearchException if either algorithm or encoding does not exist.
*/
@VisibleForTesting
@NonNull
static int calculateHashCodeMd5(@Nullable String str) throws
NoSuchAlgorithmException, UnsupportedEncodingException {
if (str == null) {
// Just return -1 if caller doesn't have database name
// For some stats like globalQuery, databaseName can be null.
// Since in atom it is an integer, we have to return something here.
return -1;
}
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 logStatsImplLocked functions to reduce code
* duplication.
*/
// TODO(b/173532925) Once we add CTS test for logging atoms and can inspect the result, we can
// remove this @VisibleForTesting and directly use PlatformLogger.logStats to test sampling and
// rate limiting.
@VisibleForTesting
@GuardedBy("mLock")
@NonNull
ExtraStats createExtraStatsLocked(@Nullable String packageName,
@CallStats.CallType int callType) {
int packageUid = Process.INVALID_UID;
if (packageName != null) {
packageUid = getPackageUidAsUserLocked(packageName);
}
// The sampling ratio here might be different from the one used in
// shouldLogForTypeLocked if there is a config change in the middle.
// Since it is only one sample, we can just ignore this difference.
// Or we can retrieve samplingRatio at beginning and pass along
// as function parameter, but it will make code less cleaner with some duplication.
int samplingInterval = getSamplingIntervalFromConfig(callType);
int skippedSampleCount = mSkippedSampleCountLocked.get(callType,
/*valueOfKeyIfNotFound=*/ 0);
mSkippedSampleCountLocked.put(callType, 0);
return new ExtraStats(packageUid, samplingInterval, skippedSampleCount);
}
/**
* Checks if this stats should be logged.
*
* <p>It won't be logged if it is "sampled" out, or it is too close to the previous logged
* stats.
*/
@GuardedBy("mLock")
// TODO(b/173532925) Once we add CTS test for logging atoms and can inspect the result, we can
// remove this @VisibleForTesting and directly use PlatformLogger.logStats to test sampling and
// rate limiting.
@VisibleForTesting
boolean shouldLogForTypeLocked(@CallStats.CallType int callType) {
int samplingInterval = getSamplingIntervalFromConfig(callType);
// Sampling
if (!shouldSample(samplingInterval)) {
return false;
}
// Rate limiting
// Check the timestamp to see if it is too close to last logged sample
long currentTimeMillis = SystemClock.elapsedRealtime();
if (mLastPushTimeMillisLocked
> currentTimeMillis - mConfig.getCachedMinTimeIntervalBetweenSamplesMillis()) {
int count = mSkippedSampleCountLocked.get(callType, /*valueOfKeyIfNotFound=*/ 0);
++count;
mSkippedSampleCountLocked.put(callType, count);
return false;
}
return true;
}
/**
* Checks if the stats should be "sampled"
*
* @param samplingInterval sampling interval
* @return if the stats should be sampled
*/
private boolean shouldSample(int samplingInterval) {
if (samplingInterval <= 0) {
return false;
}
return mRng.nextInt((int) samplingInterval) == 0;
}
/**
* Finds the UID of the {@code packageName}. Returns {@link Process#INVALID_UID} if unable to
* find the UID.
*/
@GuardedBy("mLock")
private int getPackageUidAsUserLocked(@NonNull String packageName) {
Integer packageUid = mPackageUidCacheLocked.get(packageName);
if (packageUid == null) {
packageUid = PackageUtil.getPackageUid(mUserContext, packageName);
if (packageUid != Process.INVALID_UID) {
mPackageUidCacheLocked.put(packageName, packageUid);
}
}
return packageUid;
}
/** Returns sampling ratio for stats type specified form {@link AppSearchConfig}. */
private int getSamplingIntervalFromConfig(@CallStats.CallType int statsType) {
switch (statsType) {
case CallStats.CALL_TYPE_PUT_DOCUMENTS:
case CallStats.CALL_TYPE_GET_DOCUMENTS:
case CallStats.CALL_TYPE_REMOVE_DOCUMENTS_BY_ID:
case CallStats.CALL_TYPE_REMOVE_DOCUMENTS_BY_SEARCH:
return mConfig.getCachedSamplingIntervalForBatchCallStats();
case CallStats.CALL_TYPE_PUT_DOCUMENT:
return mConfig.getCachedSamplingIntervalForPutDocumentStats();
case CallStats.CALL_TYPE_INITIALIZE:
return mConfig.getCachedSamplingIntervalForInitializeStats();
case CallStats.CALL_TYPE_SEARCH:
return mConfig.getCachedSamplingIntervalForSearchStats();
case CallStats.CALL_TYPE_GLOBAL_SEARCH:
return mConfig.getCachedSamplingIntervalForGlobalSearchStats();
case CallStats.CALL_TYPE_OPTIMIZE:
return mConfig.getCachedSamplingIntervalForOptimizeStats();
case CallStats.CALL_TYPE_UNKNOWN:
case CallStats.CALL_TYPE_SET_SCHEMA:
case CallStats.CALL_TYPE_GET_DOCUMENT:
case CallStats.CALL_TYPE_REMOVE_DOCUMENT_BY_ID:
case CallStats.CALL_TYPE_FLUSH:
case CallStats.CALL_TYPE_REMOVE_DOCUMENT_BY_SEARCH:
// TODO(b/173532925) Some of them above will have dedicated sampling ratio config
default:
return mConfig.getCachedSamplingIntervalDefault();
}
}
//
// Functions below are used for tests only
//
@VisibleForTesting
@GuardedBy("mLock")
void setLastPushTimeMillisLocked(long lastPushElapsedTimeMillis) {
mLastPushTimeMillisLocked = lastPushElapsedTimeMillis;
}
}

View File

@@ -1,203 +0,0 @@
/*
* 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.Nullable;
import android.annotation.UserIdInt;
import android.app.StatsManager;
import android.content.Context;
import android.os.UserHandle;
import android.util.Log;
import android.util.StatsEvent;
import com.android.server.appsearch.AppSearchUserInstance;
import com.android.server.appsearch.AppSearchUserInstanceManager;
import com.google.android.icing.proto.DocumentStorageInfoProto;
import com.google.android.icing.proto.IndexStorageInfoProto;
import com.google.android.icing.proto.SchemaStoreStorageInfoProto;
import com.google.android.icing.proto.StorageInfoProto;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
/**
* Implements statsd pullers for AppSearch.
*
* <p>This class registers pullers to statsd, which will be called once a day to obtain AppSearch
* statistics that cannot be sent to statsd in real time by {@link PlatformLogger}.
*
* @hide
*/
public final class StatsCollector implements StatsManager.StatsPullAtomCallback {
private static final String TAG = "AppSearchStatsCollector";
private static volatile StatsCollector sStatsCollector;
private final StatsManager mStatsManager;
/**
* Gets an instance of {@link StatsCollector} 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 StatsCollector getInstance(@NonNull Context context,
@NonNull Executor executor) {
Objects.requireNonNull(context);
Objects.requireNonNull(executor);
if (sStatsCollector == null) {
synchronized (StatsCollector.class) {
if (sStatsCollector == null) {
sStatsCollector = new StatsCollector(context, executor);
}
}
}
return sStatsCollector;
}
private StatsCollector(@NonNull Context context, @NonNull Executor executor) {
mStatsManager = context.getSystemService(StatsManager.class);
if (mStatsManager != null) {
registerAtom(AppSearchStatsLog.APP_SEARCH_STORAGE_INFO, /*policy=*/ null, executor);
Log.d(TAG, "atoms registered");
} else {
Log.e(TAG, "could not get StatsManager, atoms not registered");
}
}
/**
* {@inheritDoc}
*
* @return {@link StatsManager#PULL_SUCCESS} with list of atoms (potentially empty) if pull
* succeeded, {@link StatsManager#PULL_SKIP} if pull was too frequent or atom ID is
* unexpected.
*/
@Override
public int onPullAtom(int atomTag, @NonNull List<StatsEvent> data) {
Objects.requireNonNull(data);
switch (atomTag) {
case AppSearchStatsLog.APP_SEARCH_STORAGE_INFO:
return pullAppSearchStorageInfo(data);
default:
Log.e(TAG, "unexpected atom ID " + atomTag);
return StatsManager.PULL_SKIP;
}
}
private static int pullAppSearchStorageInfo(@NonNull List<StatsEvent> data) {
AppSearchUserInstanceManager userInstanceManager =
AppSearchUserInstanceManager.getInstance();
List<UserHandle> userHandles = userInstanceManager.getAllUserHandles();
for (int i = 0; i < userHandles.size(); i++) {
UserHandle userHandle = userHandles.get(i);
try {
AppSearchUserInstance userInstance = userInstanceManager.getUserInstance(
userHandle);
StorageInfoProto storageInfoProto =
userInstance.getAppSearchImpl().getRawStorageInfoProto();
data.add(buildStatsEvent(userHandle.getIdentifier(), storageInfoProto));
} catch (Throwable t) {
Log.e(TAG,
"Failed to pull the storage info for user " + userHandle.toString(),
t);
}
}
// Skip the report if there is no data.
if (data.isEmpty()) {
return StatsManager.PULL_SKIP;
}
return StatsManager.PULL_SUCCESS;
}
/**
* Registers and configures the callback for the pulled atom.
*
* @param atomId The id of the atom
* @param policy Optional metadata specifying the timeout, cool down time etc. statsD would
* use default values if it is null
* @param executor The executor in which to run the callback
*/
private void registerAtom(int atomId, @Nullable StatsManager.PullAtomMetadata policy,
@NonNull Executor executor) {
mStatsManager.setPullAtomCallback(atomId, policy, executor, /*callback=*/this);
}
private static StatsEvent buildStatsEvent(@UserIdInt int userId,
@NonNull StorageInfoProto storageInfoProto) {
return AppSearchStatsLog.buildStatsEvent(
AppSearchStatsLog.APP_SEARCH_STORAGE_INFO,
userId,
storageInfoProto.getTotalStorageSize(),
getDocumentStorageInfoBytes(storageInfoProto.getDocumentStorageInfo()),
getSchemaStoreStorageInfoBytes(storageInfoProto.getSchemaStoreStorageInfo()),
getIndexStorageInfoBytes(storageInfoProto.getIndexStorageInfo()));
}
private static byte[] getDocumentStorageInfoBytes(
@NonNull DocumentStorageInfoProto proto) {
// Make sure we only log the fields defined in the atom in case new fields are added in
// IcingLib
DocumentStorageInfoProto.Builder builder = DocumentStorageInfoProto.newBuilder();
builder.setNumAliveDocuments(proto.getNumAliveDocuments())
.setNumDeletedDocuments(proto.getNumDeletedDocuments())
.setNumExpiredDocuments(proto.getNumExpiredDocuments())
.setDocumentStoreSize(proto.getDocumentStoreSize())
.setDocumentLogSize(proto.getDocumentLogSize())
.setKeyMapperSize(proto.getKeyMapperSize())
.setDocumentIdMapperSize(proto.getDocumentIdMapperSize())
.setScoreCacheSize(proto.getScoreCacheSize())
.setFilterCacheSize(proto.getFilterCacheSize())
.setCorpusMapperSize(proto.getCorpusMapperSize())
.setCorpusScoreCacheSize(proto.getCorpusScoreCacheSize())
.setNamespaceIdMapperSize(proto.getNamespaceIdMapperSize())
.setNumNamespaces(proto.getNumNamespaces());
return builder.build().toByteArray();
}
private static byte[] getSchemaStoreStorageInfoBytes(
@NonNull SchemaStoreStorageInfoProto proto) {
// Make sure we only log the fields defined in the atom in case new fields are added in
// IcingLib
SchemaStoreStorageInfoProto.Builder builder = SchemaStoreStorageInfoProto.newBuilder();
builder.setSchemaStoreSize(proto.getSchemaStoreSize())
.setNumSchemaTypes(proto.getNumSchemaTypes())
.setNumTotalSections(proto.getNumTotalSections())
.setNumSchemaTypesSectionsExhausted(proto.getNumSchemaTypesSectionsExhausted());
return builder.build().toByteArray();
}
private static byte[] getIndexStorageInfoBytes(
@NonNull IndexStorageInfoProto proto) {
// Make sure we only log the fields defined in the atom in case new fields are added in
// IcingLib
IndexStorageInfoProto.Builder builder = IndexStorageInfoProto.newBuilder();
builder.setIndexSize(proto.getIndexSize())
.setLiteIndexLexiconSize(proto.getLiteIndexLexiconSize())
.setLiteIndexHitBufferSize(proto.getLiteIndexHitBufferSize())
.setMainIndexLexiconSize(proto.getMainIndexLexiconSize())
.setMainIndexStorageSize(proto.getMainIndexStorageSize())
.setMainIndexBlockSize(proto.getMainIndexBlockSize())
.setNumBlocks(proto.getNumBlocks())
.setMinFreeFraction(proto.getMinFreeFraction());
return builder.build().toByteArray();
}
}

View File

@@ -1,44 +0,0 @@
/*
* 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.util;
import android.annotation.NonNull;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Process;
/**
* Utilities for interacting with {@link android.content.pm.PackageManager},
* {@link android.os.UserHandle}, and other parts of dealing with apps and binder.
*
* @hide
*/
public class PackageUtil {
private PackageUtil() {}
/**
* Finds the UID of the {@code packageName} in the given {@code context}. Returns
* {@link Process#INVALID_UID} if unable to find the UID.
*/
public static int getPackageUid(@NonNull Context context, @NonNull String packageName) {
try {
return context.getPackageManager().getPackageUid(packageName, /*flags=*/ 0);
} catch (PackageManager.NameNotFoundException e) {
return Process.INVALID_UID;
}
}
}

View File

@@ -1,76 +0,0 @@
/*
* 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.visibilitystore;
import android.annotation.NonNull;
import android.util.ArrayMap;
import java.util.Map;
import java.util.Set;
/**
* Stores information about what types are hidden from platform surfaces through the
* {@link android.app.appsearch.SetSchemaRequest.Builder#setSchemaTypeDisplayedBySystem} API.
*
* This object is not thread safe.
*/
class NotDisplayedBySystemMap {
/**
* Maps packages to databases to the set of prefixed schemas that are platform-hidden within
* that database.
*/
private final Map<String, Map<String, Set<String>>> mMap = new ArrayMap<>();
/**
* 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 setNotDisplayedBySystem(
@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 database.
*/
public boolean isSchemaDisplayedBySystem(
@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 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(prefixedSchema);
}
}

View File

@@ -1,86 +0,0 @@
/*
* 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.visibilitystore;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.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";
/**
* Property that holds the list of platform-hidden schemas, as part of the visibility settings.
*/
private static final String NOT_DISPLAYED_BY_SYSTEM_PROPERTY = "notPlatformSurfaceable";
/** Property that holds nested documents of package accessible schemas. */
private static final String VISIBLE_TO_PACKAGES_PROPERTY = "packageAccessible";
/**
* Schema for the VisibilityStore's documents.
*
* <p>NOTE: If you update this, also update
* {@link com.android.server.appsearch.external.localstorage.VisibilityStore#SCHEMA_VERSION}
*/
public static final AppSearchSchema SCHEMA = new AppSearchSchema.Builder(SCHEMA_TYPE)
.addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
NOT_DISPLAYED_BY_SYSTEM_PROPERTY)
.setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
.build())
.addProperty(new AppSearchSchema.DocumentPropertyConfig.Builder(
VISIBLE_TO_PACKAGES_PROPERTY, VisibleToPackagesDocument.SCHEMA_TYPE)
.setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
.build())
.build();
public VisibilityDocument(@NonNull GenericDocument genericDocument) {
super(genericDocument);
}
@Nullable
public String[] getNotDisplayedBySystem() {
return getPropertyStringArray(NOT_DISPLAYED_BY_SYSTEM_PROPERTY);
}
@Nullable
public GenericDocument[] getVisibleToPackages() {
return getPropertyDocumentArray(VISIBLE_TO_PACKAGES_PROPERTY);
}
/** Builder for {@link VisibilityDocument}. */
public static class Builder extends GenericDocument.Builder<VisibilityDocument.Builder> {
public Builder(@NonNull String namespace, @NonNull String id) {
super(namespace, id, SCHEMA_TYPE);
}
/** Sets which prefixed schemas have opted out of platform surfacing. */
@NonNull
public Builder setNotDisplayedBySystem(@NonNull String[] notDisplayedBySystemSchemas) {
return setPropertyString(NOT_DISPLAYED_BY_SYSTEM_PROPERTY, notDisplayedBySystemSchemas);
}
/** Sets which prefixed schemas have configured package access. */
@NonNull
public Builder setVisibleToPackages(
@NonNull VisibleToPackagesDocument[] visibleToPackagesDocuments) {
return setPropertyDocument(VISIBLE_TO_PACKAGES_PROPERTY, visibleToPackagesDocuments);
}
}
}

View File

@@ -1,352 +0,0 @@
/*
* 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.visibilitystore;
import static android.Manifest.permission.READ_GLOBAL_APP_SEARCH_DATA;
import android.annotation.NonNull;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.GenericDocument;
import android.app.appsearch.GetSchemaResponse;
import android.app.appsearch.PackageIdentifier;
import android.app.appsearch.exceptions.AppSearchException;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
import com.android.server.appsearch.external.localstorage.AppSearchImpl;
import com.android.server.appsearch.external.localstorage.util.PrefixUtil;
import com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityStore;
import com.android.server.appsearch.util.PackageUtil;
import com.google.android.icing.proto.PersistType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* Manages any visibility settings for all the package's databases that AppSearchImpl knows about.
* Persists the visibility settings and reloads them on initialization.
*
* <p>The VisibilityStore creates a document for each package's databases. This document holds the
* visibility settings that apply to that package's database. The VisibilityStore also creates a
* schema for these documents and has its own package and database so that its data doesn't
* interfere with any clients' data. It persists the document and schema through AppSearchImpl.
*
* <p>These visibility settings are used to ensure AppSearch queries respect the clients' settings
* on who their data is visible to.
*
* <p>This class doesn't handle any locking itself. Its callers should handle the locking at a
* higher level.
*
* @hide
*/
public class VisibilityStoreImpl implements VisibilityStore {
/** Version for the visibility schema */
private static final int SCHEMA_VERSION = 0;
/** 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. */
private static final String ID_PREFIX = "uri:";
private final AppSearchImpl mAppSearchImpl;
// Context of the user that the call is being made as.
private final Context mUserContext;
/** Stores the schemas that are platform-hidden. All values are prefixed. */
private final NotDisplayedBySystemMap mNotDisplayedBySystemMap = new NotDisplayedBySystemMap();
/** Stores the schemas that are visible to 3p packages. All values are prefixed. */
private final VisibleToPackagesMap mVisibleToPackagesMap = new VisibleToPackagesMap();
/**
* Creates and initializes VisibilityStore.
*
* @param appSearchImpl AppSearchImpl instance
* @param userContext Context of the user that the call is being made as
*/
@NonNull
public static VisibilityStoreImpl create(
@NonNull AppSearchImpl appSearchImpl, @NonNull Context userContext)
throws AppSearchException {
return new VisibilityStoreImpl(appSearchImpl, userContext);
}
private VisibilityStoreImpl(@NonNull AppSearchImpl appSearchImpl, @NonNull Context userContext)
throws AppSearchException {
mAppSearchImpl = Objects.requireNonNull(appSearchImpl);
mUserContext = Objects.requireNonNull(userContext);
GetSchemaResponse getSchemaResponse = mAppSearchImpl.getSchema(PACKAGE_NAME, DATABASE_NAME);
boolean hasVisibilityType = false;
boolean hasVisibleToPackagesType = false;
for (AppSearchSchema schema : getSchemaResponse.getSchemas()) {
if (schema.getSchemaType().equals(VisibilityDocument.SCHEMA_TYPE)) {
hasVisibilityType = true;
} else if (schema.getSchemaType().equals(VisibleToPackagesDocument.SCHEMA_TYPE)) {
hasVisibleToPackagesType = true;
}
if (hasVisibilityType && hasVisibleToPackagesType) {
// Found both our types, can exit early.
break;
}
}
if (!hasVisibilityType || !hasVisibleToPackagesType) {
// Schema type doesn't exist yet. Add it.
mAppSearchImpl.setSchema(
PACKAGE_NAME,
DATABASE_NAME,
Arrays.asList(VisibilityDocument.SCHEMA, VisibleToPackagesDocument.SCHEMA),
/*visibilityStore=*/ null, // Avoid recursive calls
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ SCHEMA_VERSION);
}
// Populate visibility settings set
for (Map.Entry<String, Set<String>> entry :
mAppSearchImpl.getPackageToDatabases().entrySet()) {
String packageName = entry.getKey();
if (packageName.equals(PACKAGE_NAME)) {
continue; // Our own package. Skip.
}
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[] notDisplayedBySystemSchemas = visibilityDocument.getNotDisplayedBySystem();
if (notDisplayedBySystemSchemas != null) {
mNotDisplayedBySystemMap.setNotDisplayedBySystem(
packageName,
databaseName,
new ArraySet<>(notDisplayedBySystemSchemas));
}
// Update 3p package visibility settings
Map<String, Set<PackageIdentifier>> schemaToPackageIdentifierMap = new ArrayMap<>();
GenericDocument[] visibleToPackagesDocuments =
visibilityDocument.getVisibleToPackages();
if (visibleToPackagesDocuments != null) {
for (int i = 0; i < visibleToPackagesDocuments.length; i++) {
VisibleToPackagesDocument visibleToPackagesDocument =
new VisibleToPackagesDocument(visibleToPackagesDocuments[i]);
PackageIdentifier packageIdentifier =
visibleToPackagesDocument.getPackageIdentifier();
String prefixedSchema = visibleToPackagesDocument.getAccessibleSchemaType();
Set<PackageIdentifier> packageIdentifiers =
schemaToPackageIdentifierMap.get(prefixedSchema);
if (packageIdentifiers == null) {
packageIdentifiers = new ArraySet<>();
}
packageIdentifiers.add(packageIdentifier);
schemaToPackageIdentifierMap.put(prefixedSchema, packageIdentifiers);
}
}
mVisibleToPackagesMap.setVisibleToPackages(
packageName, databaseName, schemaToPackageIdentifierMap);
}
}
}
@Override
public void setVisibility(
@NonNull String packageName,
@NonNull String databaseName,
@NonNull Set<String> schemasNotDisplayedBySystem,
@NonNull Map<String, List<PackageIdentifier>> schemasVisibleToPackages)
throws AppSearchException {
Objects.requireNonNull(packageName);
Objects.requireNonNull(databaseName);
Objects.requireNonNull(schemasNotDisplayedBySystem);
Objects.requireNonNull(schemasVisibleToPackages);
// Persist the document
VisibilityDocument.Builder visibilityDocument =
new VisibilityDocument.Builder(
NAMESPACE, /*id=*/ getVisibilityDocumentId(packageName, databaseName));
if (!schemasNotDisplayedBySystem.isEmpty()) {
visibilityDocument.setNotDisplayedBySystem(
schemasNotDisplayedBySystem.toArray(new String[0]));
}
Map<String, Set<PackageIdentifier>> schemaToPackageIdentifierMap = new ArrayMap<>();
List<VisibleToPackagesDocument> visibleToPackagesDocuments = new ArrayList<>();
for (Map.Entry<String, List<PackageIdentifier>> entry :
schemasVisibleToPackages.entrySet()) {
for (int i = 0; i < entry.getValue().size(); i++) {
VisibleToPackagesDocument visibleToPackagesDocument =
new VisibleToPackagesDocument.Builder(NAMESPACE, /*id=*/ "")
.setAccessibleSchemaType(entry.getKey())
.setPackageIdentifier(entry.getValue().get(i))
.build();
visibleToPackagesDocuments.add(visibleToPackagesDocument);
}
schemaToPackageIdentifierMap.put(entry.getKey(), new ArraySet<>(entry.getValue()));
}
if (!visibleToPackagesDocuments.isEmpty()) {
visibilityDocument.setVisibleToPackages(
visibleToPackagesDocuments.toArray(new VisibleToPackagesDocument[0]));
}
mAppSearchImpl.putDocument(
PACKAGE_NAME, DATABASE_NAME, visibilityDocument.build(), /*logger=*/ null);
// Now that the visibility document has been written. Persist the newly written data.
mAppSearchImpl.persistToDisk(PersistType.Code.LITE);
// Update derived data structures.
mNotDisplayedBySystemMap.setNotDisplayedBySystem(
packageName, databaseName, schemasNotDisplayedBySystem);
mVisibleToPackagesMap.setVisibleToPackages(
packageName, databaseName, schemaToPackageIdentifierMap);
}
/**
* Checks whether the given package has access to system-surfaceable schemas.
*
* @param callerPackageName Package name of the caller.
*/
public boolean doesCallerHaveSystemAccess(@NonNull String callerPackageName) {
Objects.requireNonNull(callerPackageName);
return mUserContext.getPackageManager()
.checkPermission(READ_GLOBAL_APP_SEARCH_DATA, callerPackageName)
== PackageManager.PERMISSION_GRANTED;
}
@Override
public boolean isSchemaSearchableByCaller(
@NonNull String packageName,
@NonNull String databaseName,
@NonNull String prefixedSchema,
int callerUid,
boolean callerHasSystemAccess) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(databaseName);
Objects.requireNonNull(prefixedSchema);
if (packageName.equals(PACKAGE_NAME)) {
return false; // VisibilityStore schemas are for internal bookkeeping.
}
if (callerHasSystemAccess
&& mNotDisplayedBySystemMap.isSchemaDisplayedBySystem(
packageName, databaseName, prefixedSchema)) {
return true;
}
// May not be platform surfaceable, but might still be accessible through 3p access.
return isSchemaVisibleToPackages(packageName, databaseName, prefixedSchema, callerUid);
}
/**
* Returns whether the schema is accessible by the {@code callerUid}. Checks that the callerUid
* has one of the allowed PackageIdentifier's package. And if so, that the package also has the
* matching certificate.
*
* <p>This supports packages that have certificate rotation. As long as the specified
* certificate was once used to sign the package, the package will still be granted access. This
* does not handle packages that have been signed by multiple certificates.
*/
private boolean isSchemaVisibleToPackages(
@NonNull String packageName,
@NonNull String databaseName,
@NonNull String prefixedSchema,
int callerUid) {
Set<PackageIdentifier> packageIdentifiers =
mVisibleToPackagesMap.getAccessiblePackages(
packageName, databaseName, prefixedSchema);
if (packageIdentifiers.isEmpty()) {
return false;
}
for (PackageIdentifier packageIdentifier : packageIdentifiers) {
// 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.
// '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 packageUid =
PackageUtil.getPackageUid(mUserContext, packageIdentifier.getPackageName());
int userAppId = UserHandle.getAppId(packageUid);
if (callerAppId != userAppId) {
continue;
}
// Check that the package also has the matching certificate
if (mUserContext
.getPackageManager()
.hasSigningCertificate(
packageIdentifier.getPackageName(),
packageIdentifier.getSha256Certificate(),
PackageManager.CERT_INPUT_SHA256)) {
// The caller has the right package name and right certificate!
return true;
}
}
// If we can't verify the schema is package accessible, default to no access.
return false;
}
/**
* Adds a prefix to create a visibility store document's id.
*
* @param packageName Package to which the visibility doc refers
* @param databaseName Database to which the visibility doc refers
* @return Prefixed id
*/
@NonNull
private static String getVisibilityDocumentId(
@NonNull String packageName, @NonNull String databaseName) {
return ID_PREFIX + PrefixUtil.createPrefix(packageName, databaseName);
}
}

View File

@@ -1,105 +0,0 @@
/*
* 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.visibilitystore;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.GenericDocument;
import android.app.appsearch.PackageIdentifier;
/**
* Holds configuration about a package+cert that can access a schema.
*
* @see android.app.appsearch.SetSchemaRequest.Builder#setSchemaTypeVisibilityForPackage
*/
class VisibleToPackagesDocument extends GenericDocument {
/** Schema type for nested documents that hold package accessible information. */
public static final String SCHEMA_TYPE = "PackageAccessibleType";
/** Property that holds the package name that can access a schema. */
private static final String PACKAGE_NAME_PROPERTY = "packageName";
/** Property that holds the SHA 256 certificate of the app that can access a schema. */
private static final String SHA_256_CERT_PROPERTY = "sha256Cert";
/** Property that holds the prefixed schema type that is accessible by some package. */
private static final String ACCESSIBLE_SCHEMA_PROPERTY = "accessibleSchema";
/**
* Schema for package accessible documents, these will be nested in a top-level
* {@link VisibilityDocument}.
*
* <p>NOTE: If you update this, also update
* {@link com.android.server.appsearch.external.localstorage.VisibilityStore#SCHEMA_VERSION}
*/
public static final AppSearchSchema SCHEMA = new AppSearchSchema.Builder(SCHEMA_TYPE)
.addProperty(new AppSearchSchema.StringPropertyConfig.Builder(PACKAGE_NAME_PROPERTY)
.setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
.build())
.addProperty(new AppSearchSchema.BytesPropertyConfig.Builder(SHA_256_CERT_PROPERTY)
.setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
.build())
.addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
ACCESSIBLE_SCHEMA_PROPERTY)
.setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
.build())
.build();
VisibleToPackagesDocument(@NonNull GenericDocument genericDocument) {
super(genericDocument);
}
@Nullable
public String getAccessibleSchemaType() {
return getPropertyString(ACCESSIBLE_SCHEMA_PROPERTY);
}
/** Gets which package is able to access {@link #getAccessibleSchemaType} */
@NonNull
public PackageIdentifier getPackageIdentifier() {
String packageName = getPropertyString(PACKAGE_NAME_PROPERTY);
byte[] sha256Cert = getPropertyBytes(SHA_256_CERT_PROPERTY);
return new PackageIdentifier(packageName, sha256Cert);
}
/** Builder for {@link VisibleToPackagesDocument} instances. */
public static class Builder extends GenericDocument.Builder<VisibleToPackagesDocument.Builder> {
Builder(@NonNull String namespace, @NonNull String id) {
super(namespace, id, SCHEMA_TYPE);
}
/** Sets which prefixed schema type is accessible by the package */
@NonNull
public Builder setAccessibleSchemaType(@NonNull String schemaType) {
return setPropertyString(ACCESSIBLE_SCHEMA_PROPERTY, schemaType);
}
/** Sets which package is able to access the {@link #setAccessibleSchemaType}. */
@NonNull
public Builder setPackageIdentifier(@NonNull PackageIdentifier packageIdentifier) {
return setPropertyString(PACKAGE_NAME_PROPERTY, packageIdentifier.getPackageName())
.setPropertyBytes(SHA_256_CERT_PROPERTY,
packageIdentifier.getSha256Certificate());
}
@Override
@NonNull
public VisibleToPackagesDocument build() {
return new VisibleToPackagesDocument(super.build());
}
}
}

View File

@@ -1,85 +0,0 @@
/*
* 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.visibilitystore;
import android.annotation.NonNull;
import android.app.appsearch.PackageIdentifier;
import android.util.ArrayMap;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
/**
* Stores information about what types are accessible to which packages through the
* {@link android.app.appsearch.SetSchemaRequest.Builder#setSchemaTypeVisibilityForPackage} API.
*
* This object is not thread safe.
*/
class VisibleToPackagesMap {
/**
* Maps packages to databases to prefixed schemas to PackageIdentifiers that have access to that
* schema.
*/
private final Map<String, Map<String, Map<String, Set<PackageIdentifier>>>> mMap =
new ArrayMap<>();
/**
* Sets the prefixed schemas that have package visibility in the given database.
*
* <p>Any existing mappings for this prefix are overwritten.
*/
public void setVisibleToPackages(
@NonNull String packageName,
@NonNull String databaseName,
@NonNull Map<String, Set<PackageIdentifier>> 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);
}
/**
* Returns the set of all {@link android.app.appsearch.PackageIdentifier}s which can access the
* given schema type.
*
* <p>If no such settings exist, returns the empty set.
*/
@NonNull
public Set<PackageIdentifier> getAccessiblePackages(
@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(prefixedSchema);
if (accessiblePackages == null) {
return Collections.emptySet();
}
return accessiblePackages;
}
}

View File

@@ -1 +0,0 @@
Ie04f1ecc033faae8085afcb51eb9e40a298998d5

View File

@@ -1,39 +0,0 @@
// Copyright (C) 2020 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 {
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_license"],
}
java_library {
name: "AppSearchTestUtils",
srcs: ["java/**/*.java"],
libs: [
"androidx.test.ext.junit",
"framework",
"framework-appsearch",
"guava",
"truth-prebuilt",
],
visibility: [
"//frameworks/base/core/tests/coretests",
"//cts/hostsidetests/appsearch",
"//cts/tests:__subpackages__",
"//vendor:__subpackages__",
],
}

View File

@@ -1,229 +0,0 @@
/*
* Copyright 2020 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.testing;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.appsearch.AppSearchBatchResult;
import android.app.appsearch.AppSearchManager;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSession;
import android.app.appsearch.AppSearchSessionShim;
import android.app.appsearch.BatchResultCallback;
import android.app.appsearch.GenericDocument;
import android.app.appsearch.GetByDocumentIdRequest;
import android.app.appsearch.GetSchemaResponse;
import android.app.appsearch.PutDocumentsRequest;
import android.app.appsearch.RemoveByDocumentIdRequest;
import android.app.appsearch.ReportUsageRequest;
import android.app.appsearch.SearchResults;
import android.app.appsearch.SearchResultsShim;
import android.app.appsearch.SearchSpec;
import android.app.appsearch.SetSchemaRequest;
import android.app.appsearch.SetSchemaResponse;
import android.app.appsearch.StorageInfo;
import android.app.appsearch.exceptions.AppSearchException;
import android.content.Context;
import android.os.UserHandle;
import androidx.test.core.app.ApplicationProvider;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* This test class adapts the AppSearch Framework API to ListenableFuture, so it can be tested via
* a consistent interface.
* @hide
*/
public class AppSearchSessionShimImpl implements AppSearchSessionShim {
private final AppSearchSession mAppSearchSession;
private final ExecutorService mExecutor;
/** Creates the SearchSessionShim with given SearchContext. */
@NonNull
public static ListenableFuture<AppSearchSessionShim> createSearchSession(
@NonNull AppSearchManager.SearchContext searchContext) {
Context context = ApplicationProvider.getApplicationContext();
return createSearchSession(context, searchContext, Executors.newCachedThreadPool());
}
/** Creates the SearchSessionShim with given SearchContext for the given user. */
@NonNull
public static ListenableFuture<AppSearchSessionShim> createSearchSession(
@NonNull AppSearchManager.SearchContext searchContext, @UserIdInt int userId) {
Context context = ApplicationProvider.getApplicationContext()
.createContextAsUser(new UserHandle(userId), /*flags=*/ 0);
return createSearchSession(context, searchContext, Executors.newCachedThreadPool());
}
/** Creates the SearchSession with given Context and ExecutorService. */
@NonNull
public static ListenableFuture<AppSearchSessionShim> createSearchSession(
@NonNull Context context,
@NonNull AppSearchManager.SearchContext searchContext,
@NonNull ExecutorService executor) {
AppSearchManager appSearchManager = context.getSystemService(AppSearchManager.class);
SettableFuture<AppSearchResult<AppSearchSession>> future = SettableFuture.create();
appSearchManager.createSearchSession(searchContext, executor, future::set);
return Futures.transform(
future,
instance -> new AppSearchSessionShimImpl(instance.getResultValue(), executor),
executor);
}
private AppSearchSessionShimImpl(
@NonNull AppSearchSession session, @NonNull ExecutorService executor) {
mAppSearchSession = Objects.requireNonNull(session);
mExecutor = Objects.requireNonNull(executor);
}
@Override
@NonNull
public ListenableFuture<SetSchemaResponse> setSchema(@NonNull SetSchemaRequest request) {
SettableFuture<AppSearchResult<SetSchemaResponse>> future = SettableFuture.create();
mAppSearchSession.setSchema(request, mExecutor, mExecutor, future::set);
return Futures.transformAsync(future, this::transformResult, mExecutor);
}
@Override
@NonNull
public ListenableFuture<GetSchemaResponse> getSchema() {
SettableFuture<AppSearchResult<GetSchemaResponse>> future = SettableFuture.create();
mAppSearchSession.getSchema(mExecutor, future::set);
return Futures.transformAsync(future, this::transformResult, mExecutor);
}
@NonNull
@Override
public ListenableFuture<Set<String>> getNamespaces() {
SettableFuture<AppSearchResult<Set<String>>> future = SettableFuture.create();
mAppSearchSession.getNamespaces(mExecutor, future::set);
return Futures.transformAsync(future, this::transformResult, mExecutor);
}
@Override
@NonNull
public ListenableFuture<AppSearchBatchResult<String, Void>> put(
@NonNull PutDocumentsRequest request) {
SettableFuture<AppSearchBatchResult<String, Void>> future = SettableFuture.create();
mAppSearchSession.put(
request, mExecutor, new BatchResultCallbackAdapter<>(future));
return future;
}
@Override
@NonNull
public ListenableFuture<AppSearchBatchResult<String, GenericDocument>> getByDocumentId(
@NonNull GetByDocumentIdRequest request) {
SettableFuture<AppSearchBatchResult<String, GenericDocument>> future =
SettableFuture.create();
mAppSearchSession.getByDocumentId(
request, mExecutor, new BatchResultCallbackAdapter<>(future));
return future;
}
@Override
@NonNull
public SearchResultsShim search(
@NonNull String queryExpression, @NonNull SearchSpec searchSpec) {
SearchResults searchResults = mAppSearchSession.search(queryExpression, searchSpec);
return new SearchResultsShimImpl(searchResults, mExecutor);
}
@Override
@NonNull
public ListenableFuture<Void> reportUsage(@NonNull ReportUsageRequest request) {
SettableFuture<AppSearchResult<Void>> future = SettableFuture.create();
mAppSearchSession.reportUsage(request, mExecutor, future::set);
return Futures.transformAsync(future, this::transformResult, mExecutor);
}
@Override
@NonNull
public ListenableFuture<AppSearchBatchResult<String, Void>> remove(
@NonNull RemoveByDocumentIdRequest request) {
SettableFuture<AppSearchBatchResult<String, Void>> future = SettableFuture.create();
mAppSearchSession.remove(request, mExecutor, new BatchResultCallbackAdapter<>(future));
return future;
}
@Override
@NonNull
public ListenableFuture<Void> remove(
@NonNull String queryExpression, @NonNull SearchSpec searchSpec) {
SettableFuture<AppSearchResult<Void>> future = SettableFuture.create();
mAppSearchSession.remove(queryExpression, searchSpec, mExecutor, future::set);
return Futures.transformAsync(future, this::transformResult, mExecutor);
}
@NonNull
@Override
public ListenableFuture<StorageInfo> getStorageInfo() {
SettableFuture<AppSearchResult<StorageInfo>> future = SettableFuture.create();
mAppSearchSession.getStorageInfo(mExecutor, future::set);
return Futures.transformAsync(future, this::transformResult, mExecutor);
}
@Override
public void close() {
mAppSearchSession.close();
}
@Override
@NonNull
public ListenableFuture<Void> requestFlush() {
SettableFuture<AppSearchResult<Void>> future = SettableFuture.create();
// The data in platform will be flushed by scheduled task. AppSearchSession won't do
// anything extra flush.
future.set(AppSearchResult.newSuccessfulResult(null));
return Futures.transformAsync(future, this::transformResult, mExecutor);
}
private <T> ListenableFuture<T> transformResult(
@NonNull AppSearchResult<T> result) throws AppSearchException {
if (!result.isSuccess()) {
throw new AppSearchException(result.getResultCode(), result.getErrorMessage());
}
return Futures.immediateFuture(result.getResultValue());
}
private static final class BatchResultCallbackAdapter<K, V>
implements BatchResultCallback<K, V> {
private final SettableFuture<AppSearchBatchResult<K, V>> mFuture;
BatchResultCallbackAdapter(SettableFuture<AppSearchBatchResult<K, V>> future) {
mFuture = future;
}
@Override
public void onResult(AppSearchBatchResult<K, V> result) {
mFuture.set(result);
}
@Override
public void onSystemError(Throwable t) {
mFuture.setException(t);
}
}
}

View File

@@ -1,104 +0,0 @@
/*
* Copyright 2020 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.testing;
import android.annotation.NonNull;
import android.app.appsearch.AppSearchManager;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.GlobalSearchSession;
import android.app.appsearch.GlobalSearchSessionShim;
import android.app.appsearch.ReportSystemUsageRequest;
import android.app.appsearch.SearchResults;
import android.app.appsearch.SearchResultsShim;
import android.app.appsearch.SearchSpec;
import android.app.appsearch.exceptions.AppSearchException;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* This test class adapts the AppSearch Framework API to ListenableFuture, so it can be tested via a
* consistent interface.
*
* @hide
*/
public class GlobalSearchSessionShimImpl implements GlobalSearchSessionShim {
private final GlobalSearchSession mGlobalSearchSession;
private final ExecutorService mExecutor;
@NonNull
public static ListenableFuture<GlobalSearchSessionShim> createGlobalSearchSession() {
return createGlobalSearchSession(ApplicationProvider.getApplicationContext());
}
/** Only for use when called from a non-instrumented context. */
@NonNull
public static ListenableFuture<GlobalSearchSessionShim> createGlobalSearchSession(
@NonNull Context context) {
AppSearchManager appSearchManager = context.getSystemService(AppSearchManager.class);
SettableFuture<AppSearchResult<GlobalSearchSession>> future = SettableFuture.create();
ExecutorService executor = Executors.newCachedThreadPool();
appSearchManager.createGlobalSearchSession(executor, future::set);
return Futures.transform(
future,
instance -> new GlobalSearchSessionShimImpl(instance.getResultValue(), executor),
executor);
}
private GlobalSearchSessionShimImpl(
@NonNull GlobalSearchSession session, @NonNull ExecutorService executor) {
mGlobalSearchSession = Objects.requireNonNull(session);
mExecutor = Objects.requireNonNull(executor);
}
@NonNull
@Override
public SearchResultsShim search(
@NonNull String queryExpression, @NonNull SearchSpec searchSpec) {
SearchResults searchResults = mGlobalSearchSession.search(queryExpression, searchSpec);
return new SearchResultsShimImpl(searchResults, mExecutor);
}
@NonNull
@Override
public ListenableFuture<Void> reportSystemUsage(@NonNull ReportSystemUsageRequest request) {
SettableFuture<AppSearchResult<Void>> future = SettableFuture.create();
mGlobalSearchSession.reportSystemUsage(request, mExecutor, future::set);
return Futures.transformAsync(future, this::transformResult, mExecutor);
}
@Override
public void close() {
mGlobalSearchSession.close();
}
private <T> ListenableFuture<T> transformResult(
@NonNull AppSearchResult<T> result) throws AppSearchException {
if (!result.isSuccess()) {
throw new AppSearchException(result.getResultCode(), result.getErrorMessage());
}
return Futures.immediateFuture(result.getResultValue());
}
}

View File

@@ -1,58 +0,0 @@
/*
* Copyright 2020 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.testing;
import android.annotation.NonNull;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.SearchResult;
import android.app.appsearch.SearchResults;
import android.app.appsearch.SearchResultsShim;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
/**
* This test class adapts the AppSearch Framework API to ListenableFuture, so it can be tested via
* a consistent interface.
* @hide
*/
public class SearchResultsShimImpl implements SearchResultsShim {
private final Executor mExecutor;
private final SearchResults mSearchResults;
SearchResultsShimImpl(@NonNull SearchResults searchResults, @NonNull Executor executor) {
mExecutor = Objects.requireNonNull(executor);
mSearchResults = Objects.requireNonNull(searchResults);
}
@NonNull
public ListenableFuture<List<SearchResult>> getNextPage() {
SettableFuture<AppSearchResult<List<SearchResult>>> future = SettableFuture.create();
mSearchResults.getNextPage(mExecutor, future::set);
return Futures.transform(future, AppSearchResult::getResultValue, mExecutor);
}
@Override
public void close() {
mSearchResults.close();
}
}

View File

@@ -1,208 +0,0 @@
/*
* Copyright 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.testing;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.AppSearchSchema.PropertyConfig;
import android.app.appsearch.AppSearchSchema.StringPropertyConfig;
import android.app.appsearch.GenericDocument;
/**
* Encapsulates a {@link GenericDocument} that represent an email.
*
* <p>This class is a higher level implement of {@link GenericDocument}.
*/
public class AppSearchEmail extends GenericDocument {
/** The name of the schema type for {@link AppSearchEmail} documents. */
public static final String SCHEMA_TYPE = "builtin:Email";
private static final String KEY_FROM = "from";
private static final String KEY_TO = "to";
private static final String KEY_CC = "cc";
private static final String KEY_BCC = "bcc";
private static final String KEY_SUBJECT = "subject";
private static final String KEY_BODY = "body";
public static final AppSearchSchema SCHEMA =
new AppSearchSchema.Builder(SCHEMA_TYPE)
.addProperty(
new StringPropertyConfig.Builder(KEY_FROM)
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
.setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
.setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
.build())
.addProperty(
new StringPropertyConfig.Builder(KEY_TO)
.setCardinality(PropertyConfig.CARDINALITY_REPEATED)
.setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
.setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
.build())
.addProperty(
new StringPropertyConfig.Builder(KEY_CC)
.setCardinality(PropertyConfig.CARDINALITY_REPEATED)
.setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
.setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
.build())
.addProperty(
new StringPropertyConfig.Builder(KEY_BCC)
.setCardinality(PropertyConfig.CARDINALITY_REPEATED)
.setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
.setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
.build())
.addProperty(
new StringPropertyConfig.Builder(KEY_SUBJECT)
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
.setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
.setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
.build())
.addProperty(
new StringPropertyConfig.Builder(KEY_BODY)
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
.setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
.setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
.build())
.build();
/**
* Creates a new {@link AppSearchEmail} from the contents of an existing {@link
* GenericDocument}.
*
* @param document The {@link GenericDocument} containing the email content.
*/
public AppSearchEmail(@NonNull GenericDocument document) {
super(document);
}
/**
* Gets the from address of {@link AppSearchEmail}.
*
* @return The subject of {@link AppSearchEmail} or {@code null} if it's not been set yet.
*/
@Nullable
public String getFrom() {
return getPropertyString(KEY_FROM);
}
/**
* Gets the destination addresses of {@link AppSearchEmail}.
*
* @return The destination addresses of {@link AppSearchEmail} or {@code null} if it's not been
* set yet.
*/
@Nullable
public String[] getTo() {
return getPropertyStringArray(KEY_TO);
}
/**
* Gets the CC list of {@link AppSearchEmail}.
*
* @return The CC list of {@link AppSearchEmail} or {@code null} if it's not been set yet.
*/
@Nullable
public String[] getCc() {
return getPropertyStringArray(KEY_CC);
}
/**
* Gets the BCC list of {@link AppSearchEmail}.
*
* @return The BCC list of {@link AppSearchEmail} or {@code null} if it's not been set yet.
*/
@Nullable
public String[] getBcc() {
return getPropertyStringArray(KEY_BCC);
}
/**
* Gets the subject of {@link AppSearchEmail}.
*
* @return The value subject of {@link AppSearchEmail} or {@code null} if it's not been set yet.
*/
@Nullable
public String getSubject() {
return getPropertyString(KEY_SUBJECT);
}
/**
* Gets the body of {@link AppSearchEmail}.
*
* @return The body of {@link AppSearchEmail} or {@code null} if it's not been set yet.
*/
@Nullable
public String getBody() {
return getPropertyString(KEY_BODY);
}
/** The builder class for {@link AppSearchEmail}. */
public static class Builder extends GenericDocument.Builder<Builder> {
/**
* Creates a new {@link Builder}
*
* @param namespace The namespace of the Email.
* @param id The ID of the Email.
*/
public Builder(@NonNull String namespace, @NonNull String id) {
super(namespace, id, SCHEMA_TYPE);
}
/** Sets the from address of {@link AppSearchEmail} */
@NonNull
public Builder setFrom(@NonNull String from) {
return setPropertyString(KEY_FROM, from);
}
/** Sets the destination address of {@link AppSearchEmail} */
@NonNull
public Builder setTo(@NonNull String... to) {
return setPropertyString(KEY_TO, to);
}
/** Sets the CC list of {@link AppSearchEmail} */
@NonNull
public Builder setCc(@NonNull String... cc) {
return setPropertyString(KEY_CC, cc);
}
/** Sets the BCC list of {@link AppSearchEmail} */
@NonNull
public Builder setBcc(@NonNull String... bcc) {
return setPropertyString(KEY_BCC, bcc);
}
/** Sets the subject of {@link AppSearchEmail} */
@NonNull
public Builder setSubject(@NonNull String subject) {
return setPropertyString(KEY_SUBJECT, subject);
}
/** Sets the body of {@link AppSearchEmail} */
@NonNull
public Builder setBody(@NonNull String body) {
return setPropertyString(KEY_BODY, body);
}
/** Builds the {@link AppSearchEmail} object. */
@NonNull
@Override
public AppSearchEmail build() {
return new AppSearchEmail(super.build());
}
}
}

Some files were not shown because too many files have changed in this diff Show More