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