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:
@@ -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"],
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
adorokhine@google.com
|
|
||||||
sudheersai@google.com
|
|
||||||
yamasani@google.com
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "com.android.appsearch",
|
|
||||||
"version": 300000000
|
|
||||||
}
|
|
||||||
Binary file not shown.
@@ -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-----
|
|
||||||
Binary file not shown.
@@ -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-----
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// Signature format: 2.0
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// Signature format: 2.0
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// Signature format: 2.0
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
// Signature format: 2.0
|
|
||||||
package android.app.appsearch {
|
|
||||||
|
|
||||||
public class AppSearchManagerFrameworkInitializer {
|
|
||||||
method public static void initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// Signature format: 2.0
|
|
||||||
@@ -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
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"imports": [
|
|
||||||
{
|
|
||||||
"path": "frameworks/base/apex/appsearch/service/java/com/android/server/appsearch"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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 -> {
|
|
||||||
* 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 -> {
|
|
||||||
* 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 -> {
|
|
||||||
* 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}<{@link AppSearchSession}> 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}<{@link GlobalSearchSession}> 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>;
|
|
||||||
@@ -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];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -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>;
|
|
||||||
@@ -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];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -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}<{@link Bundle}>, 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}<{@link Bundle}> 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}<{@link List}<{@link String}>>.
|
|
||||||
*/
|
|
||||||
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}<{@link String}, {@link Void}>
|
|
||||||
* 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}<{@link String}, {@link Bundle}>
|
|
||||||
* 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}<{@link Bundle}> 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}<{@link Bundle}> 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}<{@link Bundle}> 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}<{@code Void}>.
|
|
||||||
*/
|
|
||||||
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}<{@link List}<{@link Bundle}>>, 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}<{@link Void}>.
|
|
||||||
*/
|
|
||||||
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}<{@link String}, {@link Void}>
|
|
||||||
* 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}<{@link Void}>.
|
|
||||||
*/
|
|
||||||
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}<{@link Bundle}>, 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}<{@link Void}>.
|
|
||||||
*/
|
|
||||||
void initialize(
|
|
||||||
in String packageName,
|
|
||||||
in UserHandle userHandle,
|
|
||||||
in long binderCallStartTimeMillis,
|
|
||||||
in IAppSearchResultCallback callback);
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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()
|
|
||||||
+ "}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -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.
|
|
||||||
}
|
|
||||||
@@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Ie04f1ecc033faae8085afcb51eb9e40a298998d5
|
|
||||||
@@ -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__",
|
|
||||||
],
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
Reference in New Issue
Block a user