DO NOT MERGE: Remove AppSearch from Android R.
Test: presubmit Bug: 150249538 Change-Id: Idb86be6586ae400552f8ceeca5c667fba8e0c43a
This commit is contained in:
@@ -282,7 +282,6 @@ filegroup {
|
||||
filegroup {
|
||||
name: "framework-updatable-sources",
|
||||
srcs: [
|
||||
":framework-appsearch-sources",
|
||||
":framework-sdkextensions-sources",
|
||||
":framework-statsd-sources",
|
||||
":framework-tethering-srcs",
|
||||
@@ -468,7 +467,6 @@ java_library {
|
||||
defaults: ["framework-defaults"],
|
||||
srcs: [":framework-non-updatable-sources"],
|
||||
libs: [
|
||||
"framework-appsearch-stubs",
|
||||
"framework-sdkextensions-stubs-systemapi",
|
||||
"framework-statsd-stubs-module_libs_api",
|
||||
"framework-permission-stubs-systemapi",
|
||||
@@ -493,7 +491,6 @@ java_library {
|
||||
visibility: [
|
||||
"//frameworks/base",
|
||||
// TODO(b/147128803) remove the below lines
|
||||
"//frameworks/base/apex/appsearch/framework",
|
||||
"//frameworks/base/apex/blobstore/framework",
|
||||
"//frameworks/base/apex/jobscheduler/framework",
|
||||
"//frameworks/base/apex/statsd/service",
|
||||
@@ -516,7 +513,6 @@ java_library {
|
||||
installable: false, // this lib is a build-only library
|
||||
static_libs: [
|
||||
"framework-minus-apex",
|
||||
"framework-appsearch", // TODO(b/146218515): should be framework-appsearch-stubs
|
||||
"framework-media-stubs-systemapi",
|
||||
"framework-mediaprovider-stubs-systemapi",
|
||||
"framework-permission-stubs-systemapi",
|
||||
@@ -541,7 +537,6 @@ java_library {
|
||||
"exoplayer2-extractor",
|
||||
"android.hardware.wifi-V1.0-java-constants",
|
||||
],
|
||||
libs: ["icing-java-proto-lite"],
|
||||
apex_available: ["//apex_available:platform"],
|
||||
visibility: [
|
||||
// DO NOT ADD ANY MORE ENTRIES TO THIS LIST
|
||||
@@ -562,7 +557,6 @@ java_library {
|
||||
libs: [
|
||||
"app-compat-annotations",
|
||||
"ext",
|
||||
"icing-java-proto-lite",
|
||||
"unsupportedappusage",
|
||||
],
|
||||
|
||||
|
||||
@@ -1,37 +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.
|
||||
|
||||
apex {
|
||||
name: "com.android.appsearch",
|
||||
manifest: "apex_manifest.json",
|
||||
java_libs: [
|
||||
"framework-appsearch",
|
||||
"service-appsearch",
|
||||
],
|
||||
key: "com.android.appsearch.key",
|
||||
certificate: ":com.android.appsearch.certificate",
|
||||
}
|
||||
|
||||
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",
|
||||
}
|
||||
@@ -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,83 +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.
|
||||
|
||||
filegroup {
|
||||
name: "framework-appsearch-sources",
|
||||
srcs: [
|
||||
"java/**/*.java",
|
||||
"java/**/*.aidl",
|
||||
],
|
||||
path: "java",
|
||||
}
|
||||
|
||||
java_library {
|
||||
name: "framework-appsearch",
|
||||
installable: true,
|
||||
sdk_version: "core_platform", // TODO(b/146218515) should be core_current
|
||||
srcs: [":framework-appsearch-sources"],
|
||||
hostdex: true, // for hiddenapi check
|
||||
libs: [
|
||||
"framework-minus-apex", // TODO(b/146218515) should be framework-system-stubs
|
||||
],
|
||||
static_libs: ["icing-java-proto-lite"],
|
||||
visibility: [
|
||||
// TODO(b/146218515) remove this when framework is built with the stub of appsearch
|
||||
"//frameworks/base",
|
||||
"//frameworks/base/apex/appsearch:__subpackages__",
|
||||
],
|
||||
permitted_packages: ["android.app.appsearch"],
|
||||
apex_available: ["com.android.appsearch"],
|
||||
}
|
||||
|
||||
metalava_appsearch_docs_args =
|
||||
"--hide-package com.android.server " +
|
||||
"--error UnhiddenSystemApi " +
|
||||
"--hide RequiresPermission " +
|
||||
"--hide MissingPermission " +
|
||||
"--hide BroadcastBehavior " +
|
||||
"--hide HiddenSuperclass " +
|
||||
"--hide DeprecationMismatch " +
|
||||
"--hide UnavailableSymbol " +
|
||||
"--hide SdkConstant " +
|
||||
"--hide HiddenTypeParameter " +
|
||||
"--hide Todo --hide Typo " +
|
||||
"--hide HiddenTypedefConstant " +
|
||||
"--show-annotation android.annotation.SystemApi "
|
||||
|
||||
droidstubs {
|
||||
name: "framework-appsearch-stubs-srcs",
|
||||
srcs: [
|
||||
":framework-annotations",
|
||||
":framework-appsearch-sources",
|
||||
],
|
||||
aidl: {
|
||||
include_dirs: ["frameworks/base/core/java"],
|
||||
},
|
||||
args: metalava_appsearch_docs_args,
|
||||
sdk_version: "core_current",
|
||||
libs: ["android_system_stubs_current"],
|
||||
}
|
||||
|
||||
java_library {
|
||||
name: "framework-appsearch-stubs",
|
||||
srcs: [":framework-appsearch-stubs-srcs"],
|
||||
aidl: {
|
||||
export_include_dirs: [
|
||||
"java",
|
||||
],
|
||||
},
|
||||
sdk_version: "core_current",
|
||||
libs: ["android_system_stubs_current"],
|
||||
installable: false,
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"imports": [
|
||||
{
|
||||
"path": "frameworks/base/apex/appsearch/service/java/com/android/server/appsearch"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,150 +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;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.ArrayMap;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Provides access to multiple results from a batch operation accepting multiple inputs.
|
||||
*
|
||||
* @param <KeyType> The type of the keys for {@link #getResults} and {@link #getFailures}.
|
||||
* @param <ValueType> The type of result objects associated with the keys.
|
||||
* @hide
|
||||
*/
|
||||
public class AppSearchBatchResult<KeyType, ValueType> implements Parcelable {
|
||||
@NonNull private final Map<KeyType, ValueType> mResults;
|
||||
@NonNull private final Map<KeyType, Throwable> mFailures;
|
||||
|
||||
private AppSearchBatchResult(
|
||||
@NonNull Map<KeyType, ValueType> results, @NonNull Map<KeyType, Throwable> failures) {
|
||||
mResults = results;
|
||||
mFailures = failures;
|
||||
}
|
||||
|
||||
private AppSearchBatchResult(@NonNull Parcel in) {
|
||||
mResults = Collections.unmodifiableMap(in.readHashMap(/*loader=*/ null));
|
||||
mFailures = Collections.unmodifiableMap(in.readHashMap(/*loader=*/ null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeMap(mResults);
|
||||
dest.writeMap(mFailures);
|
||||
}
|
||||
|
||||
/** Returns {@code true} if this {@link AppSearchBatchResult} has no failures. */
|
||||
public boolean isSuccess() {
|
||||
return mFailures.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Map} of all successful keys mapped to the results they produced.
|
||||
*
|
||||
* <p>The values of the {@link Map} may be {@code null}.
|
||||
*/
|
||||
@NonNull
|
||||
public Map<KeyType, ValueType> getResults() {
|
||||
return mResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Map} of all failed keys mapped to a {@link Throwable} representing the cause
|
||||
* of failure.
|
||||
*
|
||||
* <p>The values of the {@link Map} may be {@code null}.
|
||||
*/
|
||||
@NonNull
|
||||
public Map<KeyType, Throwable> getFailures() {
|
||||
return mFailures;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static final Creator<AppSearchBatchResult> CREATOR =
|
||||
new Creator<AppSearchBatchResult>() {
|
||||
@NonNull
|
||||
@Override
|
||||
public AppSearchBatchResult createFromParcel(@NonNull Parcel in) {
|
||||
return new AppSearchBatchResult(in);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public AppSearchBatchResult[] newArray(int size) {
|
||||
return new AppSearchBatchResult[size];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new {@link Builder} for this {@link AppSearchBatchResult}.
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public static <KeyType, ValueType> Builder<KeyType, ValueType> newBuilder() {
|
||||
return new Builder<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for {@link AppSearchBatchResult} objects.
|
||||
*
|
||||
* @param <KeyType> The type of keys.
|
||||
* @param <ValueType> The type of result objects associated with the keys.
|
||||
* @hide
|
||||
*/
|
||||
public static final class Builder<KeyType, ValueType> {
|
||||
@NonNull private final Map<KeyType, ValueType> mResults = new ArrayMap<>();
|
||||
@NonNull private final Map<KeyType, Throwable> mFailures = new ArrayMap<>();
|
||||
|
||||
private Builder() {}
|
||||
|
||||
/**
|
||||
* Registers that the {@code key} was processed successfully and associates it with
|
||||
* {@code value}. Any previous mapping for a key, whether success or failure, is deleted.
|
||||
*/
|
||||
public Builder setSuccess(@NonNull KeyType key, @Nullable ValueType value) {
|
||||
mResults.put(key, value);
|
||||
mFailures.remove(key);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers that the {@code key} failed and associates it with {@code throwable}. Any
|
||||
* previous mapping for a key, whether success or failure, is deleted.
|
||||
*/
|
||||
public Builder setFailure(@NonNull KeyType key, @Nullable Throwable throwable) {
|
||||
mFailures.put(key, throwable);
|
||||
mResults.remove(key);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Builds an {@link AppSearchBatchResult} from the contents of this {@link Builder}. */
|
||||
@NonNull
|
||||
public AppSearchBatchResult<KeyType, ValueType> build() {
|
||||
return new AppSearchBatchResult<>(mResults, mFailures);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,724 +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.CurrentTimeMillisLong;
|
||||
import android.annotation.DurationMillisLong;
|
||||
import android.annotation.IntRange;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import com.google.android.icing.proto.DocumentProto;
|
||||
import com.google.android.icing.proto.PropertyProto;
|
||||
import com.google.android.icing.protobuf.ByteString;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Represents a document unit.
|
||||
*
|
||||
* <p>Documents are constructed via {@link AppSearchDocument.Builder}.
|
||||
* @hide
|
||||
*/
|
||||
public class AppSearchDocument {
|
||||
private static final String TAG = "AppSearchDocument";
|
||||
|
||||
/**
|
||||
* The maximum number of elements in a repeatable field. Will reject the request if exceed
|
||||
* this limit.
|
||||
*/
|
||||
private static final int MAX_REPEATED_PROPERTY_LENGTH = 100;
|
||||
|
||||
/**
|
||||
* The maximum {@link String#length} of a {@link String} field. Will reject the request if
|
||||
* {@link String}s longer than this.
|
||||
*/
|
||||
private static final int MAX_STRING_LENGTH = 20_000;
|
||||
|
||||
/**
|
||||
* Contains {@link AppSearchDocument} basic information (uri, schemaType etc) and properties
|
||||
* ordered by keys.
|
||||
*/
|
||||
@NonNull
|
||||
private final DocumentProto mProto;
|
||||
|
||||
/** Contains all properties in {@link #mProto} to support getting properties via keys. */
|
||||
@NonNull
|
||||
private final Map<String, Object> mProperties;
|
||||
|
||||
/**
|
||||
* Create a new {@link AppSearchDocument}.
|
||||
* @param proto Contains {@link AppSearchDocument} basic information (uri, schemaType etc) and
|
||||
* properties ordered by keys.
|
||||
* @param propertiesMap Contains all properties in {@link #mProto} to support get properties
|
||||
* via keys.
|
||||
*/
|
||||
private AppSearchDocument(@NonNull DocumentProto proto,
|
||||
@NonNull Map<String, Object> propertiesMap) {
|
||||
mProto = proto;
|
||||
mProperties = propertiesMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link AppSearchDocument} from an existing instance.
|
||||
*
|
||||
* <p>This method should be only used by constructor of a subclass.
|
||||
*/
|
||||
protected AppSearchDocument(@NonNull AppSearchDocument document) {
|
||||
this(document.mProto, document.mProperties);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
AppSearchDocument(@NonNull DocumentProto documentProto) {
|
||||
this(documentProto, new ArrayMap<>());
|
||||
for (int i = 0; i < documentProto.getPropertiesCount(); i++) {
|
||||
PropertyProto property = documentProto.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);
|
||||
}
|
||||
mProperties.put(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);
|
||||
}
|
||||
mProperties.put(property.getName(), 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);
|
||||
}
|
||||
mProperties.put(property.getName(), 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);
|
||||
}
|
||||
mProperties.put(property.getName(), 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();
|
||||
}
|
||||
mProperties.put(name, values);
|
||||
} else if (property.getDocumentValuesCount() > 0) {
|
||||
AppSearchDocument[] values =
|
||||
new AppSearchDocument[property.getDocumentValuesCount()];
|
||||
for (int j = 0; j < values.length; j++) {
|
||||
values[j] = new AppSearchDocument(property.getDocumentValues(j));
|
||||
}
|
||||
mProperties.put(name, values);
|
||||
} else {
|
||||
throw new IllegalStateException("Unknown type of value: " + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link DocumentProto} of the {@link AppSearchDocument}.
|
||||
*
|
||||
* <p>The {@link DocumentProto} contains {@link AppSearchDocument}'s basic information and all
|
||||
* properties ordered by keys.
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
@VisibleForTesting
|
||||
public DocumentProto getProto() {
|
||||
return mProto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the uri of the {@link AppSearchDocument}.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public String getUri() {
|
||||
return mProto.getUri();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the schema type of the {@link AppSearchDocument}.
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public String getSchemaType() {
|
||||
return mProto.getSchema();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the creation timestamp in milliseconds of the {@link AppSearchDocument}. Value will be in
|
||||
* the {@link System#currentTimeMillis()} time base.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@CurrentTimeMillisLong
|
||||
public long getCreationTimestampMillis() {
|
||||
return mProto.getCreationTimestampMs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the TTL (Time To Live) of the {@link AppSearchDocument}, in milliseconds.
|
||||
*
|
||||
* <p>The default value is 0, which means the document is permanent and won't be auto-deleted
|
||||
* until the app is uninstalled.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@DurationMillisLong
|
||||
public long getTtlMillis() {
|
||||
return mProto.getTtlMs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the score of the {@link AppSearchDocument}.
|
||||
*
|
||||
* <p>The score is a query-independent measure of the document's quality, relative to other
|
||||
* {@link AppSearchDocument}s of the same type.
|
||||
*
|
||||
* <p>The default value is 0.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public int getScore() {
|
||||
return mProto.getScore();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a {@link String} value by key.
|
||||
*
|
||||
* @param key The key to look for.
|
||||
* @return The first {@link String} associated with the given key or {@code null} if there
|
||||
* is no such key or the value is of a different type.
|
||||
* @hide
|
||||
*/
|
||||
@Nullable
|
||||
public String getPropertyString(@NonNull String key) {
|
||||
String[] propertyArray = getPropertyStringArray(key);
|
||||
if (ArrayUtils.isEmpty(propertyArray)) {
|
||||
return null;
|
||||
}
|
||||
warnIfSinglePropertyTooLong("String", key, propertyArray.length);
|
||||
return propertyArray[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a {@link Long} value by key.
|
||||
*
|
||||
* @param key The key to look for.
|
||||
* @return The first {@link Long} associated with the given key or {@code null} if there
|
||||
* is no such key or the value is of a different type.
|
||||
* @hide
|
||||
*/
|
||||
@Nullable
|
||||
public Long getPropertyLong(@NonNull String key) {
|
||||
long[] propertyArray = getPropertyLongArray(key);
|
||||
if (ArrayUtils.isEmpty(propertyArray)) {
|
||||
return null;
|
||||
}
|
||||
warnIfSinglePropertyTooLong("Long", key, propertyArray.length);
|
||||
return propertyArray[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a {@link Double} value by key.
|
||||
*
|
||||
* @param key The key to look for.
|
||||
* @return The first {@link Double} associated with the given key or {@code null} if there
|
||||
* is no such key or the value is of a different type.
|
||||
* @hide
|
||||
*/
|
||||
@Nullable
|
||||
public Double getPropertyDouble(@NonNull String key) {
|
||||
double[] propertyArray = getPropertyDoubleArray(key);
|
||||
// TODO(tytytyww): Add support double array to ArraysUtils.isEmpty().
|
||||
if (propertyArray == null || propertyArray.length == 0) {
|
||||
return null;
|
||||
}
|
||||
warnIfSinglePropertyTooLong("Double", key, propertyArray.length);
|
||||
return propertyArray[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a {@link Boolean} value by key.
|
||||
*
|
||||
* @param key The key to look for.
|
||||
* @return The first {@link Boolean} associated with the given key or {@code null} if there
|
||||
* is no such key or the value is of a different type.
|
||||
* @hide
|
||||
*/
|
||||
@Nullable
|
||||
public Boolean getPropertyBoolean(@NonNull String key) {
|
||||
boolean[] propertyArray = getPropertyBooleanArray(key);
|
||||
if (ArrayUtils.isEmpty(propertyArray)) {
|
||||
return null;
|
||||
}
|
||||
warnIfSinglePropertyTooLong("Boolean", key, propertyArray.length);
|
||||
return propertyArray[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a {@code byte[]} value by key.
|
||||
*
|
||||
* @param key The key to look for.
|
||||
* @return The first {@code byte[]} associated with the given key or {@code null} if there
|
||||
* is no such key or the value is of a different type.
|
||||
*/
|
||||
@Nullable
|
||||
public byte[] getPropertyBytes(@NonNull String key) {
|
||||
byte[][] propertyArray = getPropertyBytesArray(key);
|
||||
if (ArrayUtils.isEmpty(propertyArray)) {
|
||||
return null;
|
||||
}
|
||||
warnIfSinglePropertyTooLong("ByteArray", key, propertyArray.length);
|
||||
return propertyArray[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a {@link AppSearchDocument} value by key.
|
||||
*
|
||||
* @param key The key to look for.
|
||||
* @return The first {@link AppSearchDocument} associated with the given key or {@code null} if
|
||||
* there is no such key or the value is of a different type.
|
||||
*/
|
||||
@Nullable
|
||||
public AppSearchDocument getPropertyDocument(@NonNull String key) {
|
||||
AppSearchDocument[] propertyArray = getPropertyDocumentArray(key);
|
||||
if (ArrayUtils.isEmpty(propertyArray)) {
|
||||
return null;
|
||||
}
|
||||
warnIfSinglePropertyTooLong("Document", key, propertyArray.length);
|
||||
return propertyArray[0];
|
||||
}
|
||||
|
||||
/** Prints a warning to logcat if the given propertyLength is greater than 1. */
|
||||
private static void warnIfSinglePropertyTooLong(
|
||||
@NonNull String propertyType, @NonNull String key, int propertyLength) {
|
||||
if (propertyLength > 1) {
|
||||
Log.w(TAG, "The value for \"" + key + "\" contains " + propertyLength
|
||||
+ " elements. Only the first one will be returned from "
|
||||
+ "getProperty" + propertyType + "(). Try getProperty" + propertyType
|
||||
+ "Array().");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a repeated {@code String} property by key.
|
||||
*
|
||||
* @param key The key to look for.
|
||||
* @return The {@code String[]} associated with the given key, or {@code null} if no value
|
||||
* is set or the value is of a different type.
|
||||
* @hide
|
||||
*/
|
||||
@Nullable
|
||||
public String[] getPropertyStringArray(@NonNull String key) {
|
||||
return getAndCastPropertyArray(key, String[].class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a repeated {@code long} property by key.
|
||||
*
|
||||
* @param key The key to look for.
|
||||
* @return The {@code long[]} associated with the given key, or {@code null} if no value is
|
||||
* set or the value is of a different type.
|
||||
* @hide
|
||||
*/
|
||||
@Nullable
|
||||
public long[] getPropertyLongArray(@NonNull String key) {
|
||||
return getAndCastPropertyArray(key, long[].class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a repeated {@code double} property by key.
|
||||
*
|
||||
* @param key The key to look for.
|
||||
* @return The {@code double[]} associated with the given key, or {@code null} if no value
|
||||
* is set or the value is of a different type.
|
||||
* @hide
|
||||
*/
|
||||
@Nullable
|
||||
public double[] getPropertyDoubleArray(@NonNull String key) {
|
||||
return getAndCastPropertyArray(key, double[].class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a repeated {@code boolean} property by key.
|
||||
*
|
||||
* @param key The key to look for.
|
||||
* @return The {@code boolean[]} associated with the given key, or {@code null} if no value
|
||||
* is set or the value is of a different type.
|
||||
* @hide
|
||||
*/
|
||||
@Nullable
|
||||
public boolean[] getPropertyBooleanArray(@NonNull String key) {
|
||||
return getAndCastPropertyArray(key, boolean[].class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a {@code byte[][]} property by key.
|
||||
*
|
||||
* @param key The key to look for.
|
||||
* @return The {@code byte[][]} associated with the given key, or {@code null} if no value
|
||||
* is set or the value is of a different type.
|
||||
*/
|
||||
@Nullable
|
||||
public byte[][] getPropertyBytesArray(@NonNull String key) {
|
||||
return getAndCastPropertyArray(key, byte[][].class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a repeated {@link AppSearchDocument} property by key.
|
||||
*
|
||||
* @param key The key to look for.
|
||||
* @return The {@link AppSearchDocument[]} associated with the given key, or {@code null} if no
|
||||
* value is set or the value is of a different type.
|
||||
*/
|
||||
@Nullable
|
||||
public AppSearchDocument[] getPropertyDocumentArray(@NonNull String key) {
|
||||
return getAndCastPropertyArray(key, AppSearchDocument[].class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a repeated property of the given key, and casts it to the given class type, which
|
||||
* must be an array class type.
|
||||
*/
|
||||
@Nullable
|
||||
private <T> T getAndCastPropertyArray(@NonNull String key, @NonNull Class<T> tClass) {
|
||||
Object value = mProperties.get(key);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return tClass.cast(value);
|
||||
} catch (ClassCastException e) {
|
||||
Log.w(TAG, "Error casting to requested type for key \"" + key + "\"", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
// Check only proto's equality is sufficient here since all properties in
|
||||
// mProperties are ordered by keys and stored in proto.
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (!(other instanceof AppSearchDocument)) {
|
||||
return false;
|
||||
}
|
||||
AppSearchDocument otherDocument = (AppSearchDocument) other;
|
||||
return this.mProto.equals(otherDocument.mProto);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
// Hash only proto is sufficient here since all properties in mProperties are ordered by
|
||||
// keys and stored in proto.
|
||||
return mProto.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mProto.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* The builder class for {@link AppSearchDocument}.
|
||||
*
|
||||
* @param <BuilderType> Type of subclass who extend this.
|
||||
* @hide
|
||||
*/
|
||||
public static class Builder<BuilderType extends Builder> {
|
||||
|
||||
private final Map<String, Object> mProperties = new ArrayMap<>();
|
||||
private final DocumentProto.Builder mProtoBuilder = DocumentProto.newBuilder();
|
||||
private final BuilderType mBuilderTypeInstance;
|
||||
|
||||
/**
|
||||
* Create a new {@link AppSearchDocument.Builder}.
|
||||
*
|
||||
* @param uri The uri of {@link AppSearchDocument}.
|
||||
* @param schemaType The schema type of the {@link AppSearchDocument}. The passed-in
|
||||
* {@code schemaType} must be defined using {@link AppSearchManager#setSchema} prior
|
||||
* to inserting a document of this {@code schemaType} into the AppSearch index using
|
||||
* {@link AppSearchManager#putDocuments(List)}. Otherwise, the document will be
|
||||
* rejected by {@link AppSearchManager#putDocuments(List)}.
|
||||
* @hide
|
||||
*/
|
||||
public Builder(@NonNull String uri, @NonNull String schemaType) {
|
||||
mBuilderTypeInstance = (BuilderType) this;
|
||||
mProtoBuilder.setUri(uri).setSchema(schemaType);
|
||||
// Set current timestamp for creation timestamp by default.
|
||||
setCreationTimestampMillis(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the score of the {@link AppSearchDocument}.
|
||||
*
|
||||
* <p>The score is a query-independent measure of the document's quality, relative to
|
||||
* other {@link AppSearchDocument}s of the same type.
|
||||
*
|
||||
* @throws IllegalArgumentException If the provided value is negative.
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public BuilderType setScore(@IntRange(from = 0, to = Integer.MAX_VALUE) int score) {
|
||||
if (score < 0) {
|
||||
throw new IllegalArgumentException("Document score cannot be negative.");
|
||||
}
|
||||
mProtoBuilder.setScore(score);
|
||||
return mBuilderTypeInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the creation timestamp in milliseconds of the {@link AppSearchDocument}. Should be
|
||||
* set using a value obtained from the {@link System#currentTimeMillis()} time base.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public BuilderType setCreationTimestampMillis(
|
||||
@CurrentTimeMillisLong long creationTimestampMillis) {
|
||||
mProtoBuilder.setCreationTimestampMs(creationTimestampMillis);
|
||||
return mBuilderTypeInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the TTL (Time To Live) of the {@link AppSearchDocument}, in milliseconds.
|
||||
*
|
||||
* <p>After this many milliseconds since the {@link #setCreationTimestampMillis(long)}
|
||||
* creation timestamp}, the document is deleted.
|
||||
*
|
||||
* @param ttlMillis A non-negative duration in milliseconds.
|
||||
* @throws IllegalArgumentException If the provided value is negative.
|
||||
*/
|
||||
@NonNull
|
||||
public BuilderType setTtlMillis(@DurationMillisLong long ttlMillis) {
|
||||
Preconditions.checkArgumentNonNegative(
|
||||
ttlMillis, "Document ttlMillis cannot be negative.");
|
||||
mProtoBuilder.setTtlMs(ttlMillis);
|
||||
return mBuilderTypeInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets one or multiple {@code String} values for a property, replacing its previous
|
||||
* values.
|
||||
*
|
||||
* @param key The key associated with the {@code values}.
|
||||
* @param values The {@code String} values of the property.
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public BuilderType setProperty(@NonNull String key, @NonNull String... values) {
|
||||
putInPropertyMap(key, values);
|
||||
return mBuilderTypeInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets one or multiple {@code boolean} values for a property, replacing its previous
|
||||
* values.
|
||||
*
|
||||
* @param key The key associated with the {@code values}.
|
||||
* @param values The {@code boolean} values of the property.
|
||||
*/
|
||||
@NonNull
|
||||
public BuilderType setProperty(@NonNull String key, @NonNull boolean... values) {
|
||||
putInPropertyMap(key, values);
|
||||
return mBuilderTypeInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets one or multiple {@code long} values for a property, replacing its previous
|
||||
* values.
|
||||
*
|
||||
* @param key The key associated with the {@code values}.
|
||||
* @param values The {@code long} values of the property.
|
||||
*/
|
||||
@NonNull
|
||||
public BuilderType setProperty(@NonNull String key, @NonNull long... values) {
|
||||
putInPropertyMap(key, values);
|
||||
return mBuilderTypeInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets one or multiple {@code double} values for a property, replacing its previous
|
||||
* values.
|
||||
*
|
||||
* @param key The key associated with the {@code values}.
|
||||
* @param values The {@code double} values of the property.
|
||||
*/
|
||||
@NonNull
|
||||
public BuilderType setProperty(@NonNull String key, @NonNull double... values) {
|
||||
putInPropertyMap(key, values);
|
||||
return mBuilderTypeInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets one or multiple {@code byte[]} for a property, replacing its previous values.
|
||||
*
|
||||
* @param key The key associated with the {@code values}.
|
||||
* @param values The {@code byte[]} of the property.
|
||||
*/
|
||||
@NonNull
|
||||
public BuilderType setProperty(@NonNull String key, @NonNull byte[]... values) {
|
||||
putInPropertyMap(key, values);
|
||||
return mBuilderTypeInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets one or multiple {@link AppSearchDocument} values for a property, replacing its
|
||||
* previous values.
|
||||
*
|
||||
* @param key The key associated with the {@code values}.
|
||||
* @param values The {@link AppSearchDocument} values of the property.
|
||||
*/
|
||||
@NonNull
|
||||
public BuilderType setProperty(@NonNull String key, @NonNull AppSearchDocument... values) {
|
||||
putInPropertyMap(key, values);
|
||||
return mBuilderTypeInstance;
|
||||
}
|
||||
|
||||
private void putInPropertyMap(@NonNull String key, @NonNull String[] values)
|
||||
throws IllegalArgumentException {
|
||||
Objects.requireNonNull(key);
|
||||
Objects.requireNonNull(values);
|
||||
validateRepeatedPropertyLength(key, values.length);
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
if (values[i] == null) {
|
||||
throw new IllegalArgumentException("The String at " + i + " is null.");
|
||||
} else if (values[i].length() > MAX_STRING_LENGTH) {
|
||||
throw new IllegalArgumentException("The String at " + i + " length is: "
|
||||
+ values[i].length() + ", which exceeds length limit: "
|
||||
+ MAX_STRING_LENGTH + ".");
|
||||
}
|
||||
}
|
||||
mProperties.put(key, values);
|
||||
}
|
||||
|
||||
private void putInPropertyMap(@NonNull String key, @NonNull boolean[] values) {
|
||||
Objects.requireNonNull(key);
|
||||
Objects.requireNonNull(values);
|
||||
validateRepeatedPropertyLength(key, values.length);
|
||||
mProperties.put(key, values);
|
||||
}
|
||||
|
||||
private void putInPropertyMap(@NonNull String key, @NonNull double[] values) {
|
||||
Objects.requireNonNull(key);
|
||||
Objects.requireNonNull(values);
|
||||
validateRepeatedPropertyLength(key, values.length);
|
||||
mProperties.put(key, values);
|
||||
}
|
||||
|
||||
private void putInPropertyMap(@NonNull String key, @NonNull long[] values) {
|
||||
Objects.requireNonNull(key);
|
||||
Objects.requireNonNull(values);
|
||||
validateRepeatedPropertyLength(key, values.length);
|
||||
mProperties.put(key, values);
|
||||
}
|
||||
|
||||
private void putInPropertyMap(@NonNull String key, @NonNull byte[][] values) {
|
||||
Objects.requireNonNull(key);
|
||||
Objects.requireNonNull(values);
|
||||
validateRepeatedPropertyLength(key, values.length);
|
||||
mProperties.put(key, values);
|
||||
}
|
||||
|
||||
private void putInPropertyMap(@NonNull String key, @NonNull AppSearchDocument[] values) {
|
||||
Objects.requireNonNull(key);
|
||||
Objects.requireNonNull(values);
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
if (values[i] == null) {
|
||||
throw new IllegalArgumentException("The document at " + i + " is null.");
|
||||
}
|
||||
}
|
||||
validateRepeatedPropertyLength(key, values.length);
|
||||
mProperties.put(key, values);
|
||||
}
|
||||
|
||||
private static void validateRepeatedPropertyLength(@NonNull String key, int length) {
|
||||
if (length == 0) {
|
||||
throw new IllegalArgumentException("The input array is empty.");
|
||||
} else if (length > MAX_REPEATED_PROPERTY_LENGTH) {
|
||||
throw new IllegalArgumentException(
|
||||
"Repeated property \"" + key + "\" has length " + length
|
||||
+ ", which exceeds the limit of "
|
||||
+ MAX_REPEATED_PROPERTY_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the {@link AppSearchDocument} object.
|
||||
* @hide
|
||||
*/
|
||||
public AppSearchDocument build() {
|
||||
// Build proto by sorting the keys in mProperties to exclude the influence of
|
||||
// order. Therefore documents will generate same proto as long as the contents are
|
||||
// same. Note that the order of repeated fields is still preserved.
|
||||
ArrayList<String> keys = new ArrayList<>(mProperties.keySet());
|
||||
Collections.sort(keys);
|
||||
for (int i = 0; i < keys.size(); i++) {
|
||||
String name = keys.get(i);
|
||||
Object values = mProperties.get(name);
|
||||
PropertyProto.Builder propertyProto = PropertyProto.newBuilder().setName(name);
|
||||
if (values instanceof boolean[]) {
|
||||
for (boolean value : (boolean[]) values) {
|
||||
propertyProto.addBooleanValues(value);
|
||||
}
|
||||
} else if (values instanceof long[]) {
|
||||
for (long value : (long[]) values) {
|
||||
propertyProto.addInt64Values(value);
|
||||
}
|
||||
} else if (values instanceof double[]) {
|
||||
for (double value : (double[]) values) {
|
||||
propertyProto.addDoubleValues(value);
|
||||
}
|
||||
} else if (values instanceof String[]) {
|
||||
for (String value : (String[]) values) {
|
||||
propertyProto.addStringValues(value);
|
||||
}
|
||||
} else if (values instanceof AppSearchDocument[]) {
|
||||
for (AppSearchDocument value : (AppSearchDocument[]) values) {
|
||||
propertyProto.addDocumentValues(value.getProto());
|
||||
}
|
||||
} else if (values instanceof byte[][]) {
|
||||
for (byte[] value : (byte[][]) values) {
|
||||
propertyProto.addBytesValues(ByteString.copyFrom(value));
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException(
|
||||
"Property \"" + name + "\" has unsupported value type \""
|
||||
+ values.getClass().getSimpleName() + "\"");
|
||||
}
|
||||
mProtoBuilder.addProperties(propertyProto);
|
||||
}
|
||||
return new AppSearchDocument(mProtoBuilder.build(), mProperties);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,255 +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;
|
||||
import android.app.appsearch.AppSearchSchema.PropertyConfig;
|
||||
|
||||
/**
|
||||
* Encapsulates a {@link AppSearchDocument} that represent an email.
|
||||
*
|
||||
* <p>This class is a higher level implement of {@link AppSearchDocument}.
|
||||
*
|
||||
* <p>This class will eventually migrate to Jetpack, where it will become public API.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class AppSearchEmail extends AppSearchDocument {
|
||||
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";
|
||||
|
||||
/** The name of the schema type for {@link AppSearchEmail} documents.*/
|
||||
public static final String SCHEMA_TYPE = "builtin:Email";
|
||||
|
||||
public static final AppSearchSchema SCHEMA = AppSearchSchema.newBuilder(SCHEMA_TYPE)
|
||||
.addProperty(AppSearchSchema.newPropertyBuilder(KEY_FROM)
|
||||
.setDataType(PropertyConfig.DATA_TYPE_STRING)
|
||||
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
|
||||
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
|
||||
.setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
|
||||
.build()
|
||||
|
||||
).addProperty(AppSearchSchema.newPropertyBuilder(KEY_TO)
|
||||
.setDataType(PropertyConfig.DATA_TYPE_STRING)
|
||||
.setCardinality(PropertyConfig.CARDINALITY_REPEATED)
|
||||
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
|
||||
.setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
|
||||
.build()
|
||||
|
||||
).addProperty(AppSearchSchema.newPropertyBuilder(KEY_CC)
|
||||
.setDataType(PropertyConfig.DATA_TYPE_STRING)
|
||||
.setCardinality(PropertyConfig.CARDINALITY_REPEATED)
|
||||
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
|
||||
.setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
|
||||
.build()
|
||||
|
||||
).addProperty(AppSearchSchema.newPropertyBuilder(KEY_BCC)
|
||||
.setDataType(PropertyConfig.DATA_TYPE_STRING)
|
||||
.setCardinality(PropertyConfig.CARDINALITY_REPEATED)
|
||||
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
|
||||
.setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
|
||||
.build()
|
||||
|
||||
).addProperty(AppSearchSchema.newPropertyBuilder(KEY_SUBJECT)
|
||||
.setDataType(PropertyConfig.DATA_TYPE_STRING)
|
||||
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
|
||||
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
|
||||
.setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
|
||||
.build()
|
||||
|
||||
).addProperty(AppSearchSchema.newPropertyBuilder(KEY_BODY)
|
||||
.setDataType(PropertyConfig.DATA_TYPE_STRING)
|
||||
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
|
||||
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
|
||||
.setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
|
||||
.build()
|
||||
|
||||
).build();
|
||||
|
||||
/**
|
||||
* Creates a new {@link AppSearchEmail} from the contents of an existing
|
||||
* {@link AppSearchDocument}.
|
||||
*
|
||||
* @param document The {@link AppSearchDocument} containing the email content.
|
||||
*/
|
||||
public AppSearchEmail(@NonNull AppSearchDocument document) {
|
||||
super(document);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the from address of {@link AppSearchEmail}.
|
||||
*
|
||||
* @return Returns the subject of {@link AppSearchEmail} or {@code null} if it's not been set
|
||||
* yet.
|
||||
* @hide
|
||||
*/
|
||||
@Nullable
|
||||
public String getFrom() {
|
||||
return getPropertyString(KEY_FROM);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the destination addresses of {@link AppSearchEmail}.
|
||||
*
|
||||
* @return Returns the destination addresses of {@link AppSearchEmail} or {@code null} if it's
|
||||
* not been set yet.
|
||||
* @hide
|
||||
*/
|
||||
@Nullable
|
||||
public String[] getTo() {
|
||||
return getPropertyStringArray(KEY_TO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the CC list of {@link AppSearchEmail}.
|
||||
*
|
||||
* @return Returns the CC list of {@link AppSearchEmail} or {@code null} if it's not been set
|
||||
* yet.
|
||||
* @hide
|
||||
*/
|
||||
@Nullable
|
||||
public String[] getCc() {
|
||||
return getPropertyStringArray(KEY_CC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the BCC list of {@link AppSearchEmail}.
|
||||
*
|
||||
* @return Returns the BCC list of {@link AppSearchEmail} or {@code null} if it's not been set
|
||||
* yet.
|
||||
* @hide
|
||||
*/
|
||||
@Nullable
|
||||
public String[] getBcc() {
|
||||
return getPropertyStringArray(KEY_BCC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the subject of {@link AppSearchEmail}.
|
||||
*
|
||||
* @return Returns the value subject of {@link AppSearchEmail} or {@code null} if it's not been
|
||||
* set yet.
|
||||
* @hide
|
||||
*/
|
||||
@Nullable
|
||||
public String getSubject() {
|
||||
return getPropertyString(KEY_SUBJECT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the body of {@link AppSearchEmail}.
|
||||
*
|
||||
* @return Returns the body of {@link AppSearchEmail} or {@code null} if it's not been set yet.
|
||||
* @hide
|
||||
*/
|
||||
@Nullable
|
||||
public String getBody() {
|
||||
return getPropertyString(KEY_BODY);
|
||||
}
|
||||
|
||||
/**
|
||||
* The builder class for {@link AppSearchEmail}.
|
||||
* @hide
|
||||
*/
|
||||
public static class Builder extends AppSearchDocument.Builder<AppSearchEmail.Builder> {
|
||||
|
||||
/**
|
||||
* Create a new {@link AppSearchEmail.Builder}
|
||||
* @param uri The Uri of the Email.
|
||||
* @hide
|
||||
*/
|
||||
public Builder(@NonNull String uri) {
|
||||
super(uri, SCHEMA_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the from address of {@link AppSearchEmail}
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public AppSearchEmail.Builder setFrom(@NonNull String from) {
|
||||
setProperty(KEY_FROM, from);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the destination address of {@link AppSearchEmail}
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public AppSearchEmail.Builder setTo(@NonNull String... to) {
|
||||
setProperty(KEY_TO, to);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the CC list of {@link AppSearchEmail}
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public AppSearchEmail.Builder setCc(@NonNull String... cc) {
|
||||
setProperty(KEY_CC, cc);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the BCC list of {@link AppSearchEmail}
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public AppSearchEmail.Builder setBcc(@NonNull String... bcc) {
|
||||
setProperty(KEY_BCC, bcc);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the subject of {@link AppSearchEmail}
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public AppSearchEmail.Builder setSubject(@NonNull String subject) {
|
||||
setProperty(KEY_SUBJECT, subject);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the body of {@link AppSearchEmail}
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public AppSearchEmail.Builder setBody(@NonNull String body) {
|
||||
setProperty(KEY_BODY, body);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the {@link AppSearchEmail} object.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
@Override
|
||||
public AppSearchEmail build() {
|
||||
return new AppSearchEmail(super.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,293 +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.content.Context;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import com.android.internal.infra.AndroidFuture;
|
||||
|
||||
import com.google.android.icing.proto.SchemaProto;
|
||||
import com.google.android.icing.proto.SearchResultProto;
|
||||
import com.google.android.icing.proto.SearchSpecProto;
|
||||
import com.google.android.icing.proto.StatusProto;
|
||||
import com.google.android.icing.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* This class provides access to the centralized AppSearch index maintained by the system.
|
||||
*
|
||||
* <p>Apps can index structured text documents with AppSearch, which can then be retrieved through
|
||||
* the query API.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemService(Context.APP_SEARCH_SERVICE)
|
||||
public class AppSearchManager {
|
||||
private final IAppSearchManager mService;
|
||||
|
||||
/** @hide */
|
||||
public AppSearchManager(@NonNull IAppSearchManager service) {
|
||||
mService = service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the schema being used by documents provided to the {@link #putDocuments} method.
|
||||
*
|
||||
* <p>The schema provided here is compared to the stored copy of the schema previously supplied
|
||||
* to {@link #setSchema}, if any, to determine how to treat existing documents. The following
|
||||
* types of schema modifications are always safe and are made without deleting any existing
|
||||
* documents:
|
||||
* <ul>
|
||||
* <li>Addition of new types
|
||||
* <li>Addition of new
|
||||
* {@link android.app.appsearch.AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL
|
||||
* OPTIONAL} or
|
||||
* {@link android.app.appsearch.AppSearchSchema.PropertyConfig#CARDINALITY_REPEATED
|
||||
* REPEATED} properties to a type
|
||||
* <li>Changing the cardinality of a data type to be less restrictive (e.g. changing an
|
||||
* {@link android.app.appsearch.AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL
|
||||
* OPTIONAL} property into a
|
||||
* {@link android.app.appsearch.AppSearchSchema.PropertyConfig#CARDINALITY_REPEATED
|
||||
* REPEATED} property.
|
||||
* </ul>
|
||||
*
|
||||
* <p>The following types of schema changes are not backwards-compatible. Supplying a schema
|
||||
* with such changes will result in this call throwing an {@link IllegalSchemaException}
|
||||
* describing the incompatibility, and the previously set schema will remain active:
|
||||
* <ul>
|
||||
* <li>Removal of an existing type
|
||||
* <li>Removal of a property from a type
|
||||
* <li>Changing the data type ({@code boolean}, {@code long}, etc.) of an existing property
|
||||
* <li>For properties of {@code Document} type, changing the schema type of
|
||||
* {@code Document Documents} of that property
|
||||
* <li>Changing the cardinality of a data type to be more restrictive (e.g. changing an
|
||||
* {@link android.app.appsearch.AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL
|
||||
* OPTIONAL} property into a
|
||||
* {@link android.app.appsearch.AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED
|
||||
* REQUIRED} property).
|
||||
* <li>Adding a
|
||||
* {@link android.app.appsearch.AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED
|
||||
* REQUIRED} property.
|
||||
* </ul>
|
||||
*
|
||||
* <p>If you need to make non-backwards-compatible changes as described above, instead use the
|
||||
* {@link #setSchema(List, boolean)} method with the {@code forceOverride} parameter set to
|
||||
* {@code true}.
|
||||
*
|
||||
* <p>It is a no-op to set the same schema as has been previously set; this is handled
|
||||
* efficiently.
|
||||
*
|
||||
* @param schemas The schema configs for the types used by the calling app.
|
||||
* @throws IllegalSchemaException If the provided schema is invalid, or is incompatible with the
|
||||
* previous schema.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public void setSchema(@NonNull AppSearchSchema... schemas) {
|
||||
setSchema(Arrays.asList(schemas), /*forceOverride=*/false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the schema being used by documents provided to the {@link #putDocuments} method.
|
||||
*
|
||||
* <p>This method is similar to {@link #setSchema(AppSearchSchema...)}, except for the
|
||||
* {@code forceOverride} parameter. If a backwards-incompatible schema is specified but the
|
||||
* {@code forceOverride} parameter is set to {@code true}, instead of throwing an
|
||||
* {@link IllegalSchemaException}, all documents which are not compatible with the new schema
|
||||
* will be deleted and the incompatible schema will be applied.
|
||||
*
|
||||
* @param schemas The schema configs for the types used by the calling app.
|
||||
* @param forceOverride Whether to force the new schema to be applied even if there are
|
||||
* incompatible changes versus the previously set schema. Documents which are incompatible
|
||||
* with the new schema will be deleted.
|
||||
* @throws IllegalSchemaException If the provided schema is invalid, or is incompatible with the
|
||||
* previous schema and the {@code forceOverride} parameter is set to {@code false}.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public void setSchema(@NonNull List<AppSearchSchema> schemas, boolean forceOverride) {
|
||||
// Prepare the merged schema for transmission.
|
||||
SchemaProto.Builder schemaProtoBuilder = SchemaProto.newBuilder();
|
||||
for (AppSearchSchema schema : schemas) {
|
||||
schemaProtoBuilder.addTypes(schema.getProto());
|
||||
}
|
||||
|
||||
// Serialize and send the schema.
|
||||
// TODO: This should use com.android.internal.infra.RemoteStream or another mechanism to
|
||||
// avoid binder limits.
|
||||
byte[] schemaBytes = schemaProtoBuilder.build().toByteArray();
|
||||
AndroidFuture<Void> future = new AndroidFuture<>();
|
||||
try {
|
||||
mService.setSchema(schemaBytes, forceOverride, future);
|
||||
} catch (RemoteException e) {
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
getFutureOrThrow(future);
|
||||
}
|
||||
|
||||
/**
|
||||
* Index {@link AppSearchDocument Documents} into AppSearch.
|
||||
*
|
||||
* <p>You should not call this method directly; instead, use the
|
||||
* {@code AppSearch#putDocuments()} API provided by JetPack.
|
||||
*
|
||||
* <p>Each {@link AppSearchDocument Document's} {@code schemaType} field must be set to the
|
||||
* name of a schema type previously registered via the {@link #setSchema} method.
|
||||
*
|
||||
* @param documents {@link AppSearchDocument Documents} that need to be indexed.
|
||||
* @return An {@link AppSearchBatchResult} mapping the document URIs to {@link Void} if they
|
||||
* were successfully indexed, or a {@link Throwable} describing the failure if they could
|
||||
* not be indexed.
|
||||
* @hide
|
||||
*/
|
||||
public AppSearchBatchResult<String, Void> putDocuments(
|
||||
@NonNull List<AppSearchDocument> documents) {
|
||||
// TODO(b/146386470): Transmit these documents as a RemoteStream instead of sending them in
|
||||
// one big list.
|
||||
List<byte[]> documentsBytes = new ArrayList<>(documents.size());
|
||||
for (AppSearchDocument document : documents) {
|
||||
documentsBytes.add(document.getProto().toByteArray());
|
||||
}
|
||||
AndroidFuture<AppSearchBatchResult> future = new AndroidFuture<>();
|
||||
try {
|
||||
mService.putDocuments(documentsBytes, future);
|
||||
} catch (RemoteException e) {
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
return getFutureOrThrow(future);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method searches for documents based on a given query string. It also accepts
|
||||
* specifications regarding how to search and format the results.
|
||||
*
|
||||
*<p>Currently we support following features in the raw query format:
|
||||
* <ul>
|
||||
* <li>AND
|
||||
* <p>AND joins (e.g. “match documents that have both the terms ‘dog’ and
|
||||
* ‘cat’”).
|
||||
* Example: hello world matches documents that have both ‘hello’ and ‘world’
|
||||
* <li>OR
|
||||
* <p>OR joins (e.g. “match documents that have either the term ‘dog’ or
|
||||
* ‘cat’”).
|
||||
* Example: dog OR puppy
|
||||
* <li>Exclusion
|
||||
* <p>Exclude a term (e.g. “match documents that do
|
||||
* not have the term ‘dog’”).
|
||||
* Example: -dog excludes the term ‘dog’
|
||||
* <li>Grouping terms
|
||||
* <p>Allow for conceptual grouping of subqueries to enable hierarchical structures (e.g.
|
||||
* “match documents that have either ‘dog’ or ‘puppy’, and either ‘cat’ or ‘kitten’”).
|
||||
* Example: (dog puppy) (cat kitten) two one group containing two terms.
|
||||
* <li>Property restricts
|
||||
* <p> Specifies which properties of a document to specifically match terms in (e.g.
|
||||
* “match documents where the ‘subject’ property contains ‘important’”).
|
||||
* Example: subject:important matches documents with the term ‘important’ in the
|
||||
* ‘subject’ property
|
||||
* <li>Schema type restricts
|
||||
* <p>This is similar to property restricts, but allows for restricts on top-level document
|
||||
* fields, such as schema_type. Clients should be able to limit their query to documents of
|
||||
* a certain schema_type (e.g. “match documents that are of the ‘Email’ schema_type”).
|
||||
* Example: { schema_type_filters: “Email”, “Video”,query: “dog” } will match documents
|
||||
* that contain the query term ‘dog’ and are of either the ‘Email’ schema type or the
|
||||
* ‘Video’ schema type.
|
||||
* </ul>
|
||||
*
|
||||
* <p> It is strongly recommended to use Jetpack APIs.
|
||||
*
|
||||
* @param queryExpression Query String to search.
|
||||
* @param searchSpec Spec for setting filters, raw query etc.
|
||||
* @param executor Executor on which to invoke the callback.
|
||||
* @param callback Callback to receive errors resulting from the query operation. If the
|
||||
* operation succeeds, the callback will be invoked with {@code null}.
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public void query(
|
||||
@NonNull String queryExpression,
|
||||
@NonNull SearchSpec searchSpec,
|
||||
@NonNull @CallbackExecutor Executor executor,
|
||||
@NonNull BiConsumer<? super SearchResults, ? super Throwable> callback) {
|
||||
AndroidFuture<byte[]> future = new AndroidFuture<>();
|
||||
future.whenCompleteAsync((searchResultBytes, err) -> {
|
||||
if (err != null) {
|
||||
callback.accept(null, err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (searchResultBytes != null) {
|
||||
SearchResultProto searchResultProto;
|
||||
try {
|
||||
searchResultProto = SearchResultProto.parseFrom(searchResultBytes);
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
callback.accept(null, e);
|
||||
return;
|
||||
}
|
||||
if (searchResultProto.getStatus().getCode() != StatusProto.Code.OK) {
|
||||
// TODO(sidchhabra): Add better exception handling.
|
||||
callback.accept(
|
||||
null,
|
||||
new RuntimeException(searchResultProto.getStatus().getMessage()));
|
||||
return;
|
||||
}
|
||||
SearchResults searchResults = new SearchResults(searchResultProto);
|
||||
callback.accept(searchResults, null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Nothing was supplied in the future at all
|
||||
callback.accept(
|
||||
null, new IllegalStateException("Unknown failure occurred while querying"));
|
||||
}, executor);
|
||||
|
||||
try {
|
||||
SearchSpecProto searchSpecProto = searchSpec.getSearchSpecProto();
|
||||
searchSpecProto = searchSpecProto.toBuilder().setQuery(queryExpression).build();
|
||||
mService.query(searchSpecProto.toByteArray(),
|
||||
searchSpec.getResultSpecProto().toByteArray(),
|
||||
searchSpec.getScoringSpecProto().toByteArray(), future);
|
||||
} catch (RemoteException e) {
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> T getFutureOrThrow(@NonNull AndroidFuture<T> future) {
|
||||
try {
|
||||
return future.get();
|
||||
} catch (Throwable e) {
|
||||
if (e instanceof ExecutionException) {
|
||||
e = e.getCause();
|
||||
}
|
||||
if (e instanceof RuntimeException) {
|
||||
throw (RuntimeException) e;
|
||||
}
|
||||
if (e instanceof Error) {
|
||||
throw (Error) e;
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +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.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.registerStaticService(
|
||||
Context.APP_SEARCH_SERVICE, AppSearchManager.class,
|
||||
(service) -> new AppSearchManager(IAppSearchManager.Stub.asInterface(service)));
|
||||
}
|
||||
}
|
||||
@@ -1,369 +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.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.util.ArraySet;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import com.google.android.icing.proto.PropertyConfigProto;
|
||||
import com.google.android.icing.proto.SchemaTypeConfigProto;
|
||||
import com.google.android.icing.proto.TermMatchType;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
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).
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public final class AppSearchSchema {
|
||||
private final SchemaTypeConfigProto mProto;
|
||||
|
||||
private AppSearchSchema(SchemaTypeConfigProto proto) {
|
||||
mProto = proto;
|
||||
}
|
||||
|
||||
/** Creates a new {@link AppSearchSchema.Builder}. */
|
||||
@NonNull
|
||||
public static AppSearchSchema.Builder newBuilder(@NonNull String typeName) {
|
||||
return new AppSearchSchema.Builder(typeName);
|
||||
}
|
||||
|
||||
/** Creates a new {@link PropertyConfig.Builder}. */
|
||||
@NonNull
|
||||
public static PropertyConfig.Builder newPropertyBuilder(@NonNull String propertyName) {
|
||||
return new PropertyConfig.Builder(propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link SchemaTypeConfigProto} populated by this builder.
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
@VisibleForTesting
|
||||
public SchemaTypeConfigProto getProto() {
|
||||
return mProto;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mProto.toString();
|
||||
}
|
||||
|
||||
/** Builder for {@link AppSearchSchema objects}. */
|
||||
public static final class Builder {
|
||||
private final SchemaTypeConfigProto.Builder mProtoBuilder =
|
||||
SchemaTypeConfigProto.newBuilder();
|
||||
|
||||
private Builder(@NonNull String typeName) {
|
||||
mProtoBuilder.setSchemaType(typeName);
|
||||
}
|
||||
|
||||
/** Adds a property to the given type. */
|
||||
@NonNull
|
||||
public AppSearchSchema.Builder addProperty(@NonNull PropertyConfig propertyConfig) {
|
||||
mProtoBuilder.addProperties(propertyConfig.mProto);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link AppSearchSchema} from the contents of this builder.
|
||||
*
|
||||
* <p>After calling this method, the builder must no longer be used.
|
||||
*/
|
||||
@NonNull
|
||||
public AppSearchSchema build() {
|
||||
Set<String> propertyNames = new ArraySet<>();
|
||||
for (PropertyConfigProto propertyConfigProto : mProtoBuilder.getPropertiesList()) {
|
||||
if (!propertyNames.add(propertyConfigProto.getPropertyName())) {
|
||||
throw new IllegalSchemaException(
|
||||
"Property defined more than once: "
|
||||
+ propertyConfigProto.getPropertyName());
|
||||
}
|
||||
}
|
||||
return new AppSearchSchema(mProtoBuilder.build());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration for a single property (field) of a document type.
|
||||
*
|
||||
* <p>For example, an {@code EmailMessage} would be a type and the {@code subject} would be
|
||||
* a property.
|
||||
*/
|
||||
public static final class PropertyConfig {
|
||||
/** Physical data-types of the contents of the property. */
|
||||
// NOTE: The integer values of these constants must match the proto enum constants in
|
||||
// com.google.android.icing.proto.PropertyConfigProto.DataType.Code.
|
||||
@IntDef(prefix = {"DATA_TYPE_"}, value = {
|
||||
DATA_TYPE_STRING,
|
||||
DATA_TYPE_INT64,
|
||||
DATA_TYPE_DOUBLE,
|
||||
DATA_TYPE_BOOLEAN,
|
||||
DATA_TYPE_BYTES,
|
||||
DATA_TYPE_DOCUMENT,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface DataType {}
|
||||
|
||||
public static final int DATA_TYPE_STRING = 1;
|
||||
public static final int DATA_TYPE_INT64 = 2;
|
||||
public static final int DATA_TYPE_DOUBLE = 3;
|
||||
public static final int DATA_TYPE_BOOLEAN = 4;
|
||||
|
||||
/** Unstructured BLOB. */
|
||||
public static final int DATA_TYPE_BYTES = 5;
|
||||
|
||||
/**
|
||||
* Indicates that the property itself is an Document, making it part a hierarchical
|
||||
* Document schema. Any property using this DataType MUST have a valid
|
||||
* {@code schemaType}.
|
||||
*/
|
||||
public static final int DATA_TYPE_DOCUMENT = 6;
|
||||
|
||||
/** The cardinality of the property (whether it is required, optional or repeated). */
|
||||
// NOTE: The integer values of these constants must match the proto enum constants in
|
||||
// com.google.android.icing.proto.PropertyConfigProto.Cardinality.Code.
|
||||
@IntDef(prefix = {"CARDINALITY_"}, 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;
|
||||
|
||||
/** Encapsulates the configurations on how AppSearch should query/index these terms. */
|
||||
@IntDef(prefix = {"INDEXING_TYPE_"}, 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.
|
||||
*
|
||||
* <p>Useful if the data type is not made up of terms (e.g.
|
||||
* {@link PropertyConfig#DATA_TYPE_DOCUMENT} or {@link PropertyConfig#DATA_TYPE_BYTES}
|
||||
* type). All the properties inside the nested property won't be indexed regardless of the
|
||||
* value of {@code indexingType} for the nested properties.
|
||||
*/
|
||||
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. */
|
||||
// NOTE: The integer values of these constants must match the proto enum constants in
|
||||
// com.google.android.icing.proto.IndexingConfig.TokenizerType.Code.
|
||||
@IntDef(prefix = {"TOKENIZER_TYPE_"}, value = {
|
||||
TOKENIZER_TYPE_NONE,
|
||||
TOKENIZER_TYPE_PLAIN,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface TokenizerType {}
|
||||
|
||||
/**
|
||||
* It is only valid for tokenizer_type to be 'NONE' if the data type is
|
||||
* {@link PropertyConfig#DATA_TYPE_DOCUMENT}.
|
||||
*/
|
||||
public static final int TOKENIZER_TYPE_NONE = 0;
|
||||
|
||||
/** Tokenization for plain text. */
|
||||
public static final int TOKENIZER_TYPE_PLAIN = 1;
|
||||
|
||||
private final PropertyConfigProto mProto;
|
||||
|
||||
private PropertyConfig(PropertyConfigProto proto) {
|
||||
mProto = proto;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mProto.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for {@link PropertyConfig}.
|
||||
*
|
||||
* <p>The following properties must be set, or {@link PropertyConfig} construction will
|
||||
* fail:
|
||||
* <ul>
|
||||
* <li>dataType
|
||||
* <li>cardinality
|
||||
* </ul>
|
||||
*
|
||||
* <p>In addition, if {@code schemaType} is {@link #DATA_TYPE_DOCUMENT}, {@code schemaType}
|
||||
* is also required.
|
||||
*/
|
||||
public static final class Builder {
|
||||
private final PropertyConfigProto.Builder mPropertyConfigProto =
|
||||
PropertyConfigProto.newBuilder();
|
||||
private final com.google.android.icing.proto.IndexingConfig.Builder
|
||||
mIndexingConfigProto =
|
||||
com.google.android.icing.proto.IndexingConfig.newBuilder();
|
||||
|
||||
private Builder(String propertyName) {
|
||||
mPropertyConfigProto.setPropertyName(propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of data the property contains (e.g. string, int, bytes, etc).
|
||||
*
|
||||
* <p>This property must be set.
|
||||
*/
|
||||
@NonNull
|
||||
public PropertyConfig.Builder setDataType(@DataType int dataType) {
|
||||
PropertyConfigProto.DataType.Code dataTypeProto =
|
||||
PropertyConfigProto.DataType.Code.forNumber(dataType);
|
||||
if (dataTypeProto == null) {
|
||||
throw new IllegalArgumentException("Invalid dataType: " + dataType);
|
||||
}
|
||||
mPropertyConfigProto.setDataType(dataTypeProto);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The logical schema-type of the contents of this property.
|
||||
*
|
||||
* <p>Only required when {@link #setDataType(int)} is set to
|
||||
* {@link #DATA_TYPE_DOCUMENT}. Otherwise, it is ignored.
|
||||
*/
|
||||
@NonNull
|
||||
public PropertyConfig.Builder setSchemaType(@NonNull String schemaType) {
|
||||
mPropertyConfigProto.setSchemaType(schemaType);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The cardinality of the property (whether it is optional, required or repeated).
|
||||
*
|
||||
* <p>This property must be set.
|
||||
*/
|
||||
@NonNull
|
||||
public PropertyConfig.Builder setCardinality(@Cardinality int cardinality) {
|
||||
PropertyConfigProto.Cardinality.Code cardinalityProto =
|
||||
PropertyConfigProto.Cardinality.Code.forNumber(cardinality);
|
||||
if (cardinalityProto == null) {
|
||||
throw new IllegalArgumentException("Invalid cardinality: " + cardinality);
|
||||
}
|
||||
mPropertyConfigProto.setCardinality(cardinalityProto);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures how a property should be indexed so that it can be retrieved by queries.
|
||||
*/
|
||||
@NonNull
|
||||
public PropertyConfig.Builder setIndexingType(@IndexingType int indexingType) {
|
||||
TermMatchType.Code termMatchTypeProto;
|
||||
switch (indexingType) {
|
||||
case INDEXING_TYPE_NONE:
|
||||
termMatchTypeProto = TermMatchType.Code.UNKNOWN;
|
||||
break;
|
||||
case INDEXING_TYPE_EXACT_TERMS:
|
||||
termMatchTypeProto = TermMatchType.Code.EXACT_ONLY;
|
||||
break;
|
||||
case INDEXING_TYPE_PREFIXES:
|
||||
termMatchTypeProto = TermMatchType.Code.PREFIX;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid indexingType: " + indexingType);
|
||||
}
|
||||
mIndexingConfigProto.setTermMatchType(termMatchTypeProto);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Configures how this property should be tokenized (split into words). */
|
||||
@NonNull
|
||||
public PropertyConfig.Builder setTokenizerType(@TokenizerType int tokenizerType) {
|
||||
com.google.android.icing.proto.IndexingConfig.TokenizerType.Code
|
||||
tokenizerTypeProto =
|
||||
com.google.android.icing.proto.IndexingConfig
|
||||
.TokenizerType.Code.forNumber(tokenizerType);
|
||||
if (tokenizerTypeProto == null) {
|
||||
throw new IllegalArgumentException("Invalid tokenizerType: " + tokenizerType);
|
||||
}
|
||||
mIndexingConfigProto.setTokenizerType(tokenizerTypeProto);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link PropertyConfig} from the contents of this builder.
|
||||
*
|
||||
* <p>After calling this method, the builder must no longer be used.
|
||||
*
|
||||
* @throws IllegalSchemaException If the property is not correctly populated (e.g.
|
||||
* missing {@code dataType}).
|
||||
*/
|
||||
@NonNull
|
||||
public PropertyConfig build() {
|
||||
mPropertyConfigProto.setIndexingConfig(mIndexingConfigProto);
|
||||
// TODO(b/147692920): Send the schema to Icing Lib for official validation, instead
|
||||
// of partially reimplementing some of the validation Icing does here.
|
||||
if (mPropertyConfigProto.getDataType()
|
||||
== PropertyConfigProto.DataType.Code.UNKNOWN) {
|
||||
throw new IllegalSchemaException("Missing field: dataType");
|
||||
}
|
||||
if (mPropertyConfigProto.getSchemaType().isEmpty()
|
||||
&& mPropertyConfigProto.getDataType()
|
||||
== PropertyConfigProto.DataType.Code.DOCUMENT) {
|
||||
throw new IllegalSchemaException(
|
||||
"Missing field: schemaType (required for configs with "
|
||||
+ "dataType = DOCUMENT)");
|
||||
}
|
||||
if (mPropertyConfigProto.getCardinality()
|
||||
== PropertyConfigProto.Cardinality.Code.UNKNOWN) {
|
||||
throw new IllegalSchemaException("Missing field: cardinality");
|
||||
}
|
||||
return new PropertyConfig(mPropertyConfigProto.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,60 +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 com.android.internal.infra.AndroidFuture;
|
||||
|
||||
parcelable AppSearchBatchResult;
|
||||
|
||||
/** {@hide} */
|
||||
interface IAppSearchManager {
|
||||
/**
|
||||
* Sets the schema.
|
||||
*
|
||||
* @param schemaBytes Serialized SchemaProto.
|
||||
* @param forceOverride Whether to apply the new schema even if it is incompatible. All
|
||||
* incompatible documents will be deleted.
|
||||
* @param callback {@link AndroidFuture}<{@link Void}>. Will be completed with
|
||||
* {@code null} upon successful completion of the setSchema call, or completed
|
||||
* exceptionally if setSchema fails.
|
||||
*/
|
||||
void setSchema(in byte[] schemaBytes, boolean forceOverride, in AndroidFuture callback);
|
||||
|
||||
/**
|
||||
* Inserts documents into the index.
|
||||
*
|
||||
* @param documentsBytes {@link List}<byte[]> of serialized DocumentProtos.
|
||||
* @param callback
|
||||
* {@link AndroidFuture}<{@link AppSearchBatchResult}<{@link String}, {@link Void}>>.
|
||||
* If the call fails to start, {@code callback} will be completed exceptionally. Otherwise,
|
||||
* {@code callback} will be completed with an
|
||||
* {@link AppSearchBatchResult}<{@link String}, {@link Void}>
|
||||
* where the keys are document URIs, and the values are {@code null}.
|
||||
*/
|
||||
void putDocuments(in List documentsBytes, in AndroidFuture<AppSearchBatchResult> callback);
|
||||
|
||||
/**
|
||||
* Searches a document based on a given specifications.
|
||||
*
|
||||
* @param searchSpecBytes Serialized SearchSpecProto.
|
||||
* @param resultSpecBytes Serialized SearchResultsProto.
|
||||
* @param scoringSpecBytes Serialized ScoringSpecProto.
|
||||
* @param callback {@link AndroidFuture}. Will be completed with a serialized
|
||||
* {@link SearchResultsProto}, or completed exceptionally if query fails.
|
||||
*/
|
||||
void query(in byte[] searchSpecBytes, in byte[] resultSpecBytes,
|
||||
in byte[] scoringSpecBytes, in AndroidFuture callback);
|
||||
}
|
||||
@@ -1,36 +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.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,36 +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;
|
||||
|
||||
/**
|
||||
* Indicates that a {@link android.app.appsearch.SearchResults} has logical inconsistencies such
|
||||
* as unpopulated mandatory fields or illegal combinations of parameters.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class IllegalSearchSpecException extends IllegalArgumentException {
|
||||
/**
|
||||
* Constructs a new {@link IllegalSearchSpecException}.
|
||||
*
|
||||
* @param message A developer-readable description of the issue with the bundle.
|
||||
*/
|
||||
public IllegalSearchSpecException(@NonNull String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -1,182 +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.util.Range;
|
||||
|
||||
import com.google.android.icing.proto.SnippetMatchProto;
|
||||
|
||||
/**
|
||||
* Snippet: It refers to a substring of text from the content of document that is returned as a
|
||||
* part of search result.
|
||||
* 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#getExactMatchPosition()} returns [29, 32]
|
||||
* <p>{@link MatchInfo#getExactMatch()} returns "foo"
|
||||
* <p>{@link MatchInfo#getSnippetPosition()} returns [29, 41]
|
||||
* <p>{@link MatchInfo#getSnippet()} returns "is foo. Another"
|
||||
* <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#getExactMatchPosition()} returns [0, 4]
|
||||
* <p>{@link MatchInfo#getExactMatch()} returns "Test"
|
||||
* <p>{@link MatchInfo#getSnippetPosition()} returns [0, 9]
|
||||
* <p>{@link MatchInfo#getSnippet()} returns "Test Name Jr."
|
||||
* <p> Match-2
|
||||
* <p>{@link MatchInfo#getPropertyPath()} returns "sender.email"
|
||||
* <p>{@link MatchInfo#getFullText()} returns "TestNameJr@gmail.com"
|
||||
* <p>{@link MatchInfo#getExactMatchPosition()} returns [0, 20]
|
||||
* <p>{@link MatchInfo#getExactMatch()} returns "TestNameJr@gmail.com"
|
||||
* <p>{@link MatchInfo#getSnippetPosition()} returns [0, 20]
|
||||
* <p>{@link MatchInfo#getSnippet()} returns "TestNameJr@gmail.com"
|
||||
* @hide
|
||||
*/
|
||||
// TODO(sidchhabra): Capture real snippet after integration with icingLib.
|
||||
public final class MatchInfo {
|
||||
|
||||
private final String mPropertyPath;
|
||||
private final SnippetMatchProto mSnippetMatch;
|
||||
private final AppSearchDocument mDocument;
|
||||
/**
|
||||
* List of content with same property path in a document when there are multiple matches in
|
||||
* repeated sections.
|
||||
*/
|
||||
private final String[] mValues;
|
||||
|
||||
/** @hide */
|
||||
public MatchInfo(@NonNull String propertyPath, @NonNull SnippetMatchProto snippetMatch,
|
||||
@NonNull AppSearchDocument document) {
|
||||
mPropertyPath = propertyPath;
|
||||
mSnippetMatch = snippetMatch;
|
||||
mDocument = document;
|
||||
// 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 (sidchhabra): getPropertyStringArray takes property name, handle for property path.
|
||||
String[] values = mDocument.getPropertyStringArray(propertyPath);
|
||||
if (values == null) {
|
||||
values = doubleToString(mDocument.getPropertyDoubleArray(propertyPath));
|
||||
}
|
||||
if (values == null) {
|
||||
values = longToString(mDocument.getPropertyLongArray(propertyPath));
|
||||
}
|
||||
if (values == null) {
|
||||
throw new IllegalStateException("No content found for requested property path!");
|
||||
}
|
||||
mValues = values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the property path corresponding to the given entry.
|
||||
* <p>Property Path: '.' - 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() {
|
||||
return mValues[mSnippetMatch.getValuesIndex()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the exact match range corresponding to the given entry.
|
||||
* <p>For class example 1 this returns [29, 32]
|
||||
*/
|
||||
@NonNull
|
||||
public Range getExactMatchPosition() {
|
||||
return new Range(mSnippetMatch.getExactMatchPosition(),
|
||||
mSnippetMatch.getExactMatchPosition() + mSnippetMatch.getExactMatchBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the exact match corresponding to the given entry.
|
||||
* <p>For class example 1 this returns "foo"
|
||||
*/
|
||||
@NonNull
|
||||
public CharSequence getExactMatch() {
|
||||
return getSubstring(getExactMatchPosition());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the snippet range corresponding to the given entry.
|
||||
* <p>For class example 1 this returns [29, 41]
|
||||
*/
|
||||
@NonNull
|
||||
public Range getSnippetPosition() {
|
||||
return new Range(mSnippetMatch.getWindowPosition(),
|
||||
mSnippetMatch.getWindowPosition() + mSnippetMatch.getWindowBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the snippet corresponding to the given entry.
|
||||
* <p>Snippet - Provides a subset of the content to display. The
|
||||
* length of this content can be changed {@link SearchSpec.Builder#setMaxSnippetSize(int)}.
|
||||
* 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(getSnippetPosition());
|
||||
}
|
||||
|
||||
private CharSequence getSubstring(Range range) {
|
||||
return getFullText()
|
||||
.substring((int) range.getLower(), (int) range.getUpper());
|
||||
}
|
||||
|
||||
/** Utility method to convert double[] to String[] */
|
||||
private String[] doubleToString(double[] values) {
|
||||
//TODO(sidchhabra): Implement the method.
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Utility method to convert long[] to String[] */
|
||||
private String[] longToString(long[] values) {
|
||||
//TODO(sidchhabra): Implement the method.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,128 +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;
|
||||
|
||||
import com.google.android.icing.proto.SearchResultProto;
|
||||
import com.google.android.icing.proto.SnippetMatchProto;
|
||||
import com.google.android.icing.proto.SnippetProto;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/**
|
||||
* SearchResults are a list of results that are returned from a query. Each result from this
|
||||
* list contains a document and may contain other fields like snippets based on request.
|
||||
* This iterator class is not thread safe.
|
||||
* @hide
|
||||
*/
|
||||
public final class SearchResults implements Iterator<SearchResults.Result> {
|
||||
|
||||
private final SearchResultProto mSearchResultProto;
|
||||
private int mNextIdx;
|
||||
|
||||
/** @hide */
|
||||
public SearchResults(SearchResultProto searchResultProto) {
|
||||
mSearchResultProto = searchResultProto;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return mNextIdx < mSearchResultProto.getResultsCount();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Result next() {
|
||||
if (!hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
Result result = new Result(mSearchResultProto.getResults(mNextIdx));
|
||||
mNextIdx++;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This class represents the result obtained from the query. It will contain the document which
|
||||
* which matched the specified query string and specifications.
|
||||
* @hide
|
||||
*/
|
||||
public static final class Result {
|
||||
private final SearchResultProto.ResultProto mResultProto;
|
||||
|
||||
@Nullable
|
||||
private AppSearchDocument mDocument;
|
||||
|
||||
private Result(SearchResultProto.ResultProto resultProto) {
|
||||
mResultProto = resultProto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains the matching {@link AppSearchDocument}.
|
||||
* @return Document object which matched the query.
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public AppSearchDocument getDocument() {
|
||||
if (mDocument == null) {
|
||||
mDocument = new AppSearchDocument(mResultProto.getDocument());
|
||||
}
|
||||
return mDocument;
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains a list of Snippets that matched the request. Only populated when requested in
|
||||
* {@link SearchSpec.Builder#setMaxSnippetSize(int)}.
|
||||
* @return List of matches based on {@link SearchSpec}, if snippeting is disabled and this
|
||||
* method is called it will return {@code null}. Users can also restrict snippet population
|
||||
* using {@link SearchSpec.Builder#setNumToSnippet} and
|
||||
* {@link SearchSpec.Builder#setNumMatchesPerProperty}, for all results after that value
|
||||
* this method will return {@code null}.
|
||||
* @hide
|
||||
*/
|
||||
// TODO(sidchhabra): Replace Document with proper constructor.
|
||||
@Nullable
|
||||
public List<MatchInfo> getMatchInfo() {
|
||||
if (!mResultProto.hasSnippet()) {
|
||||
return null;
|
||||
}
|
||||
AppSearchDocument document = getDocument();
|
||||
List<MatchInfo> matchList = new ArrayList<>();
|
||||
for (Iterator entryProtoIterator = mResultProto.getSnippet()
|
||||
.getEntriesList().iterator(); entryProtoIterator.hasNext(); ) {
|
||||
SnippetProto.EntryProto entry = (SnippetProto.EntryProto) entryProtoIterator.next();
|
||||
for (Iterator snippetMatchProtoIterator = entry.getSnippetMatchesList().iterator();
|
||||
snippetMatchProtoIterator.hasNext(); ) {
|
||||
matchList.add(new MatchInfo(entry.getPropertyName(),
|
||||
(SnippetMatchProto) snippetMatchProtoIterator.next(), document));
|
||||
}
|
||||
}
|
||||
return matchList;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mSearchResultProto.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,260 +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.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
|
||||
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.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @hide
|
||||
*/
|
||||
// TODO(sidchhabra) : AddResultSpec fields for Snippets etc.
|
||||
public final class SearchSpec {
|
||||
|
||||
private final SearchSpecProto mSearchSpecProto;
|
||||
private final ResultSpecProto mResultSpecProto;
|
||||
private final ScoringSpecProto mScoringSpecProto;
|
||||
|
||||
private SearchSpec(@NonNull SearchSpecProto searchSpecProto,
|
||||
@NonNull ResultSpecProto resultSpecProto, @NonNull ScoringSpecProto scoringSpecProto) {
|
||||
mSearchSpecProto = searchSpecProto;
|
||||
mResultSpecProto = resultSpecProto;
|
||||
mScoringSpecProto = scoringSpecProto;
|
||||
}
|
||||
|
||||
/** Creates a new {@link SearchSpec.Builder}. */
|
||||
@NonNull
|
||||
public static SearchSpec.Builder newBuilder() {
|
||||
return new SearchSpec.Builder();
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@NonNull
|
||||
SearchSpecProto getSearchSpecProto() {
|
||||
return mSearchSpecProto;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@NonNull
|
||||
ResultSpecProto getResultSpecProto() {
|
||||
return mResultSpecProto;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@NonNull
|
||||
ScoringSpecProto getScoringSpecProto() {
|
||||
return mScoringSpecProto;
|
||||
}
|
||||
|
||||
/** Term Match Type for the query. */
|
||||
// NOTE: The integer values of these constants must match the proto enum constants in
|
||||
// {@link com.google.android.icing.proto.SearchSpecProto.termMatchType}
|
||||
@IntDef(prefix = {"TERM_MATCH_TYPE_"}, value = {
|
||||
TERM_MATCH_TYPE_EXACT_ONLY,
|
||||
TERM_MATCH_TYPE_PREFIX
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface TermMatchTypeCode {}
|
||||
|
||||
/**
|
||||
* 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_TYPE_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_TYPE_PREFIX = 2;
|
||||
|
||||
/** Ranking Strategy for query result.*/
|
||||
// NOTE: The integer values of these constants must match the proto enum constants in
|
||||
// {@link ScoringSpecProto.RankingStrategy.Code }
|
||||
@IntDef(prefix = {"RANKING_STRATEGY_"}, value = {
|
||||
RANKING_STRATEGY_NONE,
|
||||
RANKING_STRATEGY_DOCUMENT_SCORE,
|
||||
RANKING_STRATEGY_CREATION_TIMESTAMP
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface RankingStrategyCode {}
|
||||
|
||||
/** 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;
|
||||
|
||||
/** Order for query result.*/
|
||||
// NOTE: The integer values of these constants must match the proto enum constants in
|
||||
// {@link ScoringSpecProto.Order.Code }
|
||||
@IntDef(prefix = {"ORDER_"}, value = {
|
||||
ORDER_DESCENDING,
|
||||
ORDER_ASCENDING
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface OrderCode {}
|
||||
|
||||
/** 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;
|
||||
|
||||
/** Builder for {@link SearchSpec objects}. */
|
||||
public static final class Builder {
|
||||
|
||||
private final SearchSpecProto.Builder mSearchSpecBuilder = SearchSpecProto.newBuilder();
|
||||
private final ResultSpecProto.Builder mResultSpecBuilder = ResultSpecProto.newBuilder();
|
||||
private final ScoringSpecProto.Builder mScoringSpecBuilder = ScoringSpecProto.newBuilder();
|
||||
private final ResultSpecProto.SnippetSpecProto.Builder mSnippetSpecBuilder =
|
||||
ResultSpecProto.SnippetSpecProto.newBuilder();
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates how the query terms should match {@link TermMatchTypeCode} in the index.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setTermMatchType(@TermMatchTypeCode int termMatchTypeCode) {
|
||||
TermMatchType.Code termMatchTypeCodeProto =
|
||||
TermMatchType.Code.forNumber(termMatchTypeCode);
|
||||
if (termMatchTypeCodeProto == null) {
|
||||
throw new IllegalArgumentException("Invalid term match type: "
|
||||
+ termMatchTypeCode);
|
||||
}
|
||||
mSearchSpecBuilder.setTermMatchType(termMatchTypeCodeProto);
|
||||
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 setSchemaTypes(@NonNull String... schemaTypes) {
|
||||
for (String schemaType : schemaTypes) {
|
||||
mSearchSpecBuilder.addSchemaTypeFilters(schemaType);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Sets the maximum number of results to retrieve from the query */
|
||||
@NonNull
|
||||
public SearchSpec.Builder setNumToRetrieve(int numToRetrieve) {
|
||||
mResultSpecBuilder.setNumToRetrieve(numToRetrieve);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Sets ranking strategy for AppSearch results.*/
|
||||
@NonNull
|
||||
public Builder setRankingStrategy(@RankingStrategyCode int rankingStrategy) {
|
||||
ScoringSpecProto.RankingStrategy.Code rankingStrategyCodeProto =
|
||||
ScoringSpecProto.RankingStrategy.Code.forNumber(rankingStrategy);
|
||||
if (rankingStrategyCodeProto == null) {
|
||||
throw new IllegalArgumentException("Invalid result ranking strategy: "
|
||||
+ rankingStrategyCodeProto);
|
||||
}
|
||||
mScoringSpecBuilder.setRankBy(rankingStrategyCodeProto);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates the order of returned search results, the default is DESC, 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(@OrderCode int order) {
|
||||
ScoringSpecProto.Order.Code orderCodeProto =
|
||||
ScoringSpecProto.Order.Code.forNumber(order);
|
||||
if (orderCodeProto == null) {
|
||||
throw new IllegalArgumentException("Invalid result ranking order: "
|
||||
+ orderCodeProto);
|
||||
}
|
||||
mScoringSpecBuilder.setOrderBy(orderCodeProto);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only the first {@code numToSnippet} documents based on the ranking strategy
|
||||
* will have snippet information provided.
|
||||
* <p>If set to 0 (default), snippeting is disabled and
|
||||
* {@link SearchResults.Result#getMatchInfo} will return {@code null} for that result.
|
||||
*/
|
||||
@NonNull
|
||||
public SearchSpec.Builder setNumToSnippet(int numToSnippet) {
|
||||
mSnippetSpecBuilder.setNumToSnippet(numToSnippet);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only the first {@code numMatchesPerProperty} matches for a every property of
|
||||
* {@link AppSearchDocument} will contain snippet information.
|
||||
* <p>If set to 0, snippeting is disabled and {@link SearchResults.Result#getMatchInfo}
|
||||
* will return {@code null} for that result.
|
||||
*/
|
||||
@NonNull
|
||||
public SearchSpec.Builder setNumMatchesPerProperty(int numMatchesPerProperty) {
|
||||
mSnippetSpecBuilder.setNumMatchesPerProperty(numMatchesPerProperty);
|
||||
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(int maxSnippetSize) {
|
||||
mSnippetSpecBuilder.setMaxWindowBytes(maxSnippetSize);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link SearchSpec} from the contents of this builder.
|
||||
*
|
||||
* <p>After calling this method, the builder must no longer be used.
|
||||
*/
|
||||
@NonNull
|
||||
public SearchSpec build() {
|
||||
if (mSearchSpecBuilder.getTermMatchType() == TermMatchType.Code.UNKNOWN) {
|
||||
throw new IllegalSearchSpecException("Missing termMatchType field.");
|
||||
}
|
||||
mResultSpecBuilder.setSnippetSpec(mSnippetSpecBuilder);
|
||||
return new SearchSpec(mSearchSpecBuilder.build(), mResultSpecBuilder.build(),
|
||||
mScoringSpecBuilder.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +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.
|
||||
java_library {
|
||||
name: "service-appsearch",
|
||||
installable: true,
|
||||
srcs: ["java/**/*.java"],
|
||||
libs: [
|
||||
"framework",
|
||||
"framework-appsearch",
|
||||
"services.core",
|
||||
],
|
||||
apex_available: ["com.android.appsearch"],
|
||||
}
|
||||
@@ -1,125 +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 com.android.server.appsearch;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.app.appsearch.AppSearchBatchResult;
|
||||
import android.app.appsearch.IAppSearchManager;
|
||||
import android.content.Context;
|
||||
import android.os.Binder;
|
||||
import android.os.UserHandle;
|
||||
|
||||
import com.android.internal.infra.AndroidFuture;
|
||||
import com.android.internal.util.Preconditions;
|
||||
import com.android.server.SystemService;
|
||||
import com.android.server.appsearch.impl.AppSearchImpl;
|
||||
import com.android.server.appsearch.impl.FakeIcing;
|
||||
import com.android.server.appsearch.impl.ImplInstanceManager;
|
||||
|
||||
import com.google.android.icing.proto.DocumentProto;
|
||||
import com.google.android.icing.proto.SchemaProto;
|
||||
import com.google.android.icing.proto.SearchResultProto;
|
||||
import com.google.android.icing.proto.SearchSpecProto;
|
||||
import com.google.android.icing.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* TODO(b/142567528): add comments when implement this class
|
||||
*/
|
||||
public class AppSearchManagerService extends SystemService {
|
||||
|
||||
public AppSearchManagerService(Context context) {
|
||||
super(context);
|
||||
mFakeIcing = new FakeIcing();
|
||||
}
|
||||
|
||||
private final FakeIcing mFakeIcing;
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
publishBinderService(Context.APP_SEARCH_SERVICE, new Stub());
|
||||
}
|
||||
|
||||
private class Stub extends IAppSearchManager.Stub {
|
||||
@Override
|
||||
public void setSchema(byte[] schemaBytes, boolean forceOverride, AndroidFuture callback) {
|
||||
Preconditions.checkNotNull(schemaBytes);
|
||||
Preconditions.checkNotNull(callback);
|
||||
int callingUid = Binder.getCallingUidOrThrow();
|
||||
int callingUserId = UserHandle.getUserId(callingUid);
|
||||
long callingIdentity = Binder.clearCallingIdentity();
|
||||
try {
|
||||
SchemaProto schema = SchemaProto.parseFrom(schemaBytes);
|
||||
AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
|
||||
impl.setSchema(callingUid, schema, forceOverride);
|
||||
callback.complete(null);
|
||||
} catch (Throwable t) {
|
||||
callback.completeExceptionally(t);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(callingIdentity);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putDocuments(
|
||||
List documentsBytes, AndroidFuture<AppSearchBatchResult> callback) {
|
||||
Preconditions.checkNotNull(documentsBytes);
|
||||
Preconditions.checkNotNull(callback);
|
||||
int callingUid = Binder.getCallingUidOrThrow();
|
||||
int callingUserId = UserHandle.getUserId(callingUid);
|
||||
long callingIdentity = Binder.clearCallingIdentity();
|
||||
try {
|
||||
AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
|
||||
AppSearchBatchResult.Builder<String, Void> resultBuilder =
|
||||
AppSearchBatchResult.newBuilder();
|
||||
for (int i = 0; i < documentsBytes.size(); i++) {
|
||||
byte[] documentBytes = (byte[]) documentsBytes.get(i);
|
||||
DocumentProto document = DocumentProto.parseFrom(documentBytes);
|
||||
try {
|
||||
impl.putDocument(callingUid, document);
|
||||
resultBuilder.setSuccess(document.getUri(), /*value=*/ null);
|
||||
} catch (Throwable t) {
|
||||
resultBuilder.setFailure(document.getUri(), t);
|
||||
}
|
||||
}
|
||||
callback.complete(resultBuilder.build());
|
||||
} catch (Throwable t) {
|
||||
callback.completeExceptionally(t);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(callingIdentity);
|
||||
}
|
||||
}
|
||||
// TODO(sidchhabra):Init FakeIcing properly.
|
||||
// TODO(sidchhabra): Do this in a threadpool.
|
||||
@Override
|
||||
public void query(@NonNull byte[] searchSpec, @NonNull byte[] resultSpec,
|
||||
@NonNull byte[] scoringSpec, AndroidFuture callback) {
|
||||
Preconditions.checkNotNull(searchSpec);
|
||||
Preconditions.checkNotNull(resultSpec);
|
||||
Preconditions.checkNotNull(scoringSpec);
|
||||
SearchSpecProto searchSpecProto = null;
|
||||
try {
|
||||
searchSpecProto = SearchSpecProto.parseFrom(searchSpec);
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
SearchResultProto searchResults =
|
||||
mFakeIcing.query(searchSpecProto.getQuery());
|
||||
callback.complete(searchResults.toByteArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"presubmit": [
|
||||
{
|
||||
"name": "CtsAppSearchTestCases"
|
||||
},
|
||||
{
|
||||
"name": "FrameworksServicesTests",
|
||||
"options": [
|
||||
{
|
||||
"include-filter": "com.android.server.appsearch"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "FrameworksCoreTests",
|
||||
"options": [
|
||||
{
|
||||
"include-filter": "android.app.appsearch"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,168 +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.impl;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.content.Context;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import com.google.android.icing.proto.DocumentProto;
|
||||
import com.google.android.icing.proto.PropertyConfigProto;
|
||||
import com.google.android.icing.proto.PropertyProto;
|
||||
import com.google.android.icing.proto.SchemaProto;
|
||||
import com.google.android.icing.proto.SchemaTypeConfigProto;
|
||||
|
||||
/**
|
||||
* Manages interaction with {@link FakeIcing} and other components to implement AppSearch
|
||||
* functionality.
|
||||
*/
|
||||
public final class AppSearchImpl {
|
||||
private final Context mContext;
|
||||
private final @UserIdInt int mUserId;
|
||||
private final FakeIcing mFakeIcing = new FakeIcing();
|
||||
|
||||
AppSearchImpl(@NonNull Context context, @UserIdInt int userId) {
|
||||
mContext = context;
|
||||
mUserId = userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the AppSearch schema for this app.
|
||||
*
|
||||
* @param callingUid The uid of the app calling AppSearch.
|
||||
* @param origSchema The schema to set for this app.
|
||||
* @param forceOverride Whether to force-apply the schema even if it is incompatible. Documents
|
||||
* which do not comply with the new schema will be deleted.
|
||||
*/
|
||||
public void setSchema(int callingUid, @NonNull SchemaProto origSchema, boolean forceOverride) {
|
||||
// Rewrite schema type names to include the calling app's package and uid.
|
||||
String typePrefix = getTypePrefix(callingUid);
|
||||
SchemaProto.Builder schemaBuilder = origSchema.toBuilder();
|
||||
rewriteSchemaTypes(typePrefix, schemaBuilder);
|
||||
|
||||
// TODO(b/145635424): Save in schema type map
|
||||
// TODO(b/145635424): Apply the schema to Icing and report results
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrites all types mentioned in the given {@code schemaBuilder} to prepend
|
||||
* {@code typePrefix}.
|
||||
*
|
||||
* @param typePrefix The prefix to add
|
||||
* @param schemaBuilder The schema to mutate
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void rewriteSchemaTypes(
|
||||
@NonNull String typePrefix, @NonNull SchemaProto.Builder schemaBuilder) {
|
||||
for (int typeIdx = 0; typeIdx < schemaBuilder.getTypesCount(); typeIdx++) {
|
||||
SchemaTypeConfigProto.Builder typeConfigBuilder =
|
||||
schemaBuilder.getTypes(typeIdx).toBuilder();
|
||||
|
||||
// Rewrite SchemaProto.types.schema_type
|
||||
String newSchemaType = typePrefix + typeConfigBuilder.getSchemaType();
|
||||
typeConfigBuilder.setSchemaType(newSchemaType);
|
||||
|
||||
// Rewrite SchemaProto.types.properties.schema_type
|
||||
for (int propertyIdx = 0;
|
||||
propertyIdx < typeConfigBuilder.getPropertiesCount();
|
||||
propertyIdx++) {
|
||||
PropertyConfigProto.Builder propertyConfigBuilder =
|
||||
typeConfigBuilder.getProperties(propertyIdx).toBuilder();
|
||||
if (!propertyConfigBuilder.getSchemaType().isEmpty()) {
|
||||
String newPropertySchemaType =
|
||||
typePrefix + propertyConfigBuilder.getSchemaType();
|
||||
propertyConfigBuilder.setSchemaType(newPropertySchemaType);
|
||||
typeConfigBuilder.setProperties(propertyIdx, propertyConfigBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
schemaBuilder.setTypes(typeIdx, typeConfigBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a document to the AppSearch index.
|
||||
*
|
||||
* @param callingUid The uid of the app calling AppSearch.
|
||||
* @param origDocument The document to index.
|
||||
*/
|
||||
public void putDocument(int callingUid, @NonNull DocumentProto origDocument) {
|
||||
// Rewrite the type names to include the app's prefix
|
||||
String typePrefix = getTypePrefix(callingUid);
|
||||
DocumentProto.Builder documentBuilder = origDocument.toBuilder();
|
||||
rewriteDocumentTypes(typePrefix, documentBuilder);
|
||||
mFakeIcing.put(documentBuilder.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrites all types mentioned anywhere in {@code documentBuilder} to prepend
|
||||
* {@code typePrefix}.
|
||||
*
|
||||
* @param typePrefix The prefix to add
|
||||
* @param documentBuilder The document to mutate
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void rewriteDocumentTypes(
|
||||
@NonNull String typePrefix,
|
||||
@NonNull DocumentProto.Builder documentBuilder) {
|
||||
// Rewrite the type name to include the app's prefix
|
||||
String newSchema = typePrefix + documentBuilder.getSchema();
|
||||
documentBuilder.setSchema(newSchema);
|
||||
|
||||
// Add namespace. If we ever allow users to set their own namespaces, this will have
|
||||
// to change to prepend the prefix instead of setting the whole namespace. We will also have
|
||||
// to store the namespaces in a map similar to the type map so we can rewrite queries with
|
||||
// empty namespaces.
|
||||
documentBuilder.setNamespace(typePrefix);
|
||||
|
||||
// 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();
|
||||
rewriteDocumentTypes(typePrefix, derivedDocumentBuilder);
|
||||
propertyBuilder.setDocumentValues(documentIdx, derivedDocumentBuilder);
|
||||
}
|
||||
documentBuilder.setProperties(propertyIdx, propertyBuilder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a type prefix in a format like {@code com.example.package@1000/} or
|
||||
* {@code com.example.sharedname:5678@1000/}.
|
||||
*/
|
||||
@NonNull
|
||||
private String getTypePrefix(int callingUid) {
|
||||
// For regular apps, this call will return the package name. If callingUid is an
|
||||
// android:sharedUserId, this value may be another type of name and have a :uid suffix.
|
||||
String callingUidName = mContext.getPackageManager().getNameForUid(callingUid);
|
||||
if (callingUidName == null) {
|
||||
// Not sure how this is possible --- maybe app was uninstalled?
|
||||
throw new IllegalStateException("Failed to look up package name for uid " + callingUid);
|
||||
}
|
||||
return callingUidName + "@" + mUserId + "/";
|
||||
}
|
||||
}
|
||||
@@ -1,169 +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.impl;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.google.android.icing.proto.DocumentProto;
|
||||
import com.google.android.icing.proto.PropertyProto;
|
||||
import com.google.android.icing.proto.SearchResultProto;
|
||||
import com.google.android.icing.proto.StatusProto;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Fake in-memory implementation of the Icing key-value store and reverse index.
|
||||
* <p>
|
||||
* Currently, only queries by single exact term are supported. There is no support for persistence,
|
||||
* namespaces, i18n tokenization, or schema.
|
||||
*/
|
||||
public class FakeIcing {
|
||||
private final AtomicInteger mNextDocId = new AtomicInteger();
|
||||
private final Map<String, Integer> mUriToDocIdMap = new ArrayMap<>();
|
||||
/** Array of Documents where index into the array is the docId. */
|
||||
private final SparseArray<DocumentProto> mDocStore = new SparseArray<>();
|
||||
/** Map of term to posting-list (the set of DocIds containing that term). */
|
||||
private final Map<String, Set<Integer>> mIndex = new ArrayMap<>();
|
||||
|
||||
/**
|
||||
* Inserts a document into the index.
|
||||
*
|
||||
* @param document The document to insert.
|
||||
*/
|
||||
public void put(@NonNull DocumentProto document) {
|
||||
String uri = document.getUri();
|
||||
|
||||
// Update mDocIdMap
|
||||
Integer docId = mUriToDocIdMap.get(uri);
|
||||
if (docId != null) {
|
||||
// Delete the old doc
|
||||
mDocStore.remove(docId);
|
||||
}
|
||||
|
||||
// Allocate a new docId
|
||||
docId = mNextDocId.getAndIncrement();
|
||||
mUriToDocIdMap.put(uri, docId);
|
||||
|
||||
// Update mDocStore
|
||||
mDocStore.put(docId, document);
|
||||
|
||||
// Update mIndex
|
||||
indexDocument(docId, document);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a document from the index.
|
||||
*
|
||||
* @param uri The URI of the document to retrieve.
|
||||
* @return The body of the document, or {@code null} if no such document exists.
|
||||
*/
|
||||
@Nullable
|
||||
public DocumentProto get(@NonNull String uri) {
|
||||
Integer docId = mUriToDocIdMap.get(uri);
|
||||
if (docId == null) {
|
||||
return null;
|
||||
}
|
||||
return mDocStore.get(docId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns documents containing the given term.
|
||||
*
|
||||
* @param term A single exact term to look up in the index.
|
||||
* @return A {@link SearchResultProto} containing the matching documents, which may have no
|
||||
* results if no documents match.
|
||||
*/
|
||||
@NonNull
|
||||
public SearchResultProto query(@NonNull String term) {
|
||||
String normTerm = normalizeString(term);
|
||||
Set<Integer> docIds = mIndex.get(normTerm);
|
||||
SearchResultProto.Builder results = SearchResultProto.newBuilder()
|
||||
.setStatus(StatusProto.newBuilder().setCode(StatusProto.Code.OK));
|
||||
if (docIds == null || docIds.isEmpty()) {
|
||||
return results.build();
|
||||
}
|
||||
|
||||
for (int docId : docIds) {
|
||||
DocumentProto document = mDocStore.get(docId);
|
||||
if (document != null) {
|
||||
results.addResults(
|
||||
SearchResultProto.ResultProto.newBuilder().setDocument(document));
|
||||
}
|
||||
}
|
||||
return results.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a document by its URI.
|
||||
*
|
||||
* @param uri The URI of the document to be deleted.
|
||||
*/
|
||||
public void delete(@NonNull String uri) {
|
||||
// Update mDocIdMap
|
||||
Integer docId = mUriToDocIdMap.get(uri);
|
||||
if (docId != null) {
|
||||
// Delete the old doc
|
||||
mDocStore.remove(docId);
|
||||
mUriToDocIdMap.remove(uri);
|
||||
}
|
||||
}
|
||||
|
||||
private void indexDocument(int docId, DocumentProto document) {
|
||||
for (PropertyProto property : document.getPropertiesList()) {
|
||||
for (String stringValue : property.getStringValuesList()) {
|
||||
String[] words = normalizeString(stringValue).split("\\s+");
|
||||
for (String word : words) {
|
||||
indexTerm(docId, word);
|
||||
}
|
||||
}
|
||||
for (Long longValue : property.getInt64ValuesList()) {
|
||||
indexTerm(docId, longValue.toString());
|
||||
}
|
||||
for (Double doubleValue : property.getDoubleValuesList()) {
|
||||
indexTerm(docId, doubleValue.toString());
|
||||
}
|
||||
for (Boolean booleanValue : property.getBooleanValuesList()) {
|
||||
indexTerm(docId, booleanValue.toString());
|
||||
}
|
||||
// Intentionally skipping bytes values
|
||||
for (DocumentProto documentValue : property.getDocumentValuesList()) {
|
||||
indexDocument(docId, documentValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void indexTerm(int docId, String term) {
|
||||
Set<Integer> postingList = mIndex.get(term);
|
||||
if (postingList == null) {
|
||||
postingList = new ArraySet<>();
|
||||
mIndex.put(term, postingList);
|
||||
}
|
||||
postingList.add(docId);
|
||||
}
|
||||
|
||||
/** Strips out punctuation and converts to lowercase. */
|
||||
private static String normalizeString(String input) {
|
||||
return input.replaceAll("\\p{P}", "").toLowerCase(Locale.getDefault());
|
||||
}
|
||||
}
|
||||
@@ -1,56 +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.impl;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.content.Context;
|
||||
import android.util.SparseArray;
|
||||
|
||||
/**
|
||||
* Manages the lifecycle of instances of {@link AppSearchImpl}.
|
||||
*
|
||||
* <p>These instances are managed per unique device-user.
|
||||
*/
|
||||
public final class ImplInstanceManager {
|
||||
private static final SparseArray<AppSearchImpl> sInstances = new SparseArray<>();
|
||||
|
||||
/**
|
||||
* Gets an instance of AppSearchImpl for the given user.
|
||||
*
|
||||
* <p>If no AppSearchImpl instance exists for this user, Icing will be initialized and one will
|
||||
* be created.
|
||||
*
|
||||
* @param context The Android context
|
||||
* @param userId The multi-user userId of the device user calling AppSearch
|
||||
* @return An initialized {@link AppSearchImpl} for this user
|
||||
*/
|
||||
@NonNull
|
||||
public static AppSearchImpl getInstance(@NonNull Context context, @UserIdInt int userId) {
|
||||
AppSearchImpl instance = sInstances.get(userId);
|
||||
if (instance == null) {
|
||||
synchronized (ImplInstanceManager.class) {
|
||||
instance = sInstances.get(userId);
|
||||
if (instance == null) {
|
||||
instance = new AppSearchImpl(context, userId);
|
||||
sInstances.put(userId, instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
@@ -921,14 +921,6 @@ package android.app.admin {
|
||||
|
||||
}
|
||||
|
||||
package android.app.appsearch {
|
||||
|
||||
public class AppSearchManagerFrameworkInitializer {
|
||||
method public static void initialize();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
package android.app.assist {
|
||||
|
||||
public static class AssistStructure.ViewNode {
|
||||
|
||||
@@ -23,7 +23,6 @@ import android.annotation.SystemApi;
|
||||
import android.app.ContextImpl.ServiceInitializationState;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.app.admin.IDevicePolicyManager;
|
||||
import android.app.appsearch.AppSearchManagerFrameworkInitializer;
|
||||
import android.app.blob.BlobStoreManagerFrameworkInitializer;
|
||||
import android.app.contentsuggestions.ContentSuggestionsManager;
|
||||
import android.app.contentsuggestions.IContentSuggestionsManager;
|
||||
@@ -1342,7 +1341,6 @@ public final class SystemServiceRegistry {
|
||||
JobSchedulerFrameworkInitializer.registerServiceWrappers();
|
||||
BlobStoreManagerFrameworkInitializer.initialize();
|
||||
TelephonyFrameworkInitializer.registerServiceWrappers();
|
||||
AppSearchManagerFrameworkInitializer.initialize();
|
||||
WifiFrameworkInitializer.registerServiceWrappers();
|
||||
StatsFrameworkInitializer.registerServiceWrappers();
|
||||
} finally {
|
||||
|
||||
@@ -5106,16 +5106,6 @@ public abstract class Context {
|
||||
@SuppressLint("ServiceName")
|
||||
public static final String BATTERY_STATS_SERVICE = "batterystats";
|
||||
|
||||
/**
|
||||
* Use with {@link #getSystemService(String)} to retrieve an
|
||||
* {@link android.app.appsearch.AppSearchManager} for
|
||||
* indexing and querying app data managed by the system.
|
||||
*
|
||||
* @see #getSystemService(String)
|
||||
* @hide
|
||||
*/
|
||||
public static final String APP_SEARCH_SERVICE = "app_search";
|
||||
|
||||
/**
|
||||
* Use with {@link #getSystemService(String)} to retrieve an
|
||||
* {@link android.content.integrity.AppIntegrityManager}.
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
|
||||
// Static whitelist of open paths that the zygote is allowed to keep open.
|
||||
static const char* kPathWhitelist[] = {
|
||||
"/apex/com.android.appsearch/javalib/framework-appsearch.jar",
|
||||
"/apex/com.android.conscrypt/javalib/conscrypt.jar",
|
||||
"/apex/com.android.ipsec/javalib/ike.jar",
|
||||
"/apex/com.android.media/javalib/updatable-media.jar",
|
||||
|
||||
@@ -56,7 +56,6 @@ android_test {
|
||||
// TODO(b/149928788): Remove this when statsd tests move into the statsd dir.
|
||||
"framework-statsd",
|
||||
"framework",
|
||||
"icing-java-proto-lite",
|
||||
"ext",
|
||||
"framework-res",
|
||||
],
|
||||
|
||||
@@ -1,262 +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 static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.testng.Assert.assertThrows;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.google.android.icing.proto.DocumentProto;
|
||||
import com.google.android.icing.proto.PropertyProto;
|
||||
import com.google.android.icing.protobuf.ByteString;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
@SmallTest
|
||||
public class AppSearchDocumentTest {
|
||||
private static final byte[] sByteArray1 = new byte[]{(byte) 1, (byte) 2, (byte) 3};
|
||||
private static final byte[] sByteArray2 = new byte[]{(byte) 4, (byte) 5, (byte) 6};
|
||||
private static final AppSearchDocument sDocumentProperties1 = new AppSearchDocument
|
||||
.Builder("sDocumentProperties1", "sDocumentPropertiesSchemaType1")
|
||||
.build();
|
||||
private static final AppSearchDocument sDocumentProperties2 = new AppSearchDocument
|
||||
.Builder("sDocumentProperties2", "sDocumentPropertiesSchemaType2")
|
||||
.build();
|
||||
|
||||
@Test
|
||||
public void testDocumentEquals_Identical() {
|
||||
AppSearchDocument document1 = new AppSearchDocument.Builder("uri1", "schemaType1")
|
||||
.setCreationTimestampMillis(5L)
|
||||
.setTtlMillis(1L)
|
||||
.setProperty("longKey1", 1L, 2L, 3L)
|
||||
.setProperty("doubleKey1", 1.0, 2.0, 3.0)
|
||||
.setProperty("booleanKey1", true, false, true)
|
||||
.setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
|
||||
.setProperty("byteKey1", sByteArray1, sByteArray2)
|
||||
.setProperty("documentKey1", sDocumentProperties1, sDocumentProperties2)
|
||||
.build();
|
||||
AppSearchDocument document2 = new AppSearchDocument.Builder("uri1", "schemaType1")
|
||||
.setCreationTimestampMillis(5L)
|
||||
.setTtlMillis(1L)
|
||||
.setProperty("longKey1", 1L, 2L, 3L)
|
||||
.setProperty("doubleKey1", 1.0, 2.0, 3.0)
|
||||
.setProperty("booleanKey1", true, false, true)
|
||||
.setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
|
||||
.setProperty("byteKey1", sByteArray1, sByteArray2)
|
||||
.setProperty("documentKey1", sDocumentProperties1, sDocumentProperties2)
|
||||
.build();
|
||||
assertThat(document1).isEqualTo(document2);
|
||||
assertThat(document1.hashCode()).isEqualTo(document2.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDocumentEquals_DifferentOrder() {
|
||||
AppSearchDocument document1 = new AppSearchDocument.Builder("uri1", "schemaType1")
|
||||
.setCreationTimestampMillis(5L)
|
||||
.setProperty("longKey1", 1L, 2L, 3L)
|
||||
.setProperty("byteKey1", sByteArray1, sByteArray2)
|
||||
.setProperty("doubleKey1", 1.0, 2.0, 3.0)
|
||||
.setProperty("booleanKey1", true, false, true)
|
||||
.setProperty("documentKey1", sDocumentProperties1, sDocumentProperties2)
|
||||
.setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
|
||||
.build();
|
||||
|
||||
// Create second document with same parameter but different order.
|
||||
AppSearchDocument document2 = new AppSearchDocument.Builder("uri1", "schemaType1")
|
||||
.setCreationTimestampMillis(5L)
|
||||
.setProperty("booleanKey1", true, false, true)
|
||||
.setProperty("documentKey1", sDocumentProperties1, sDocumentProperties2)
|
||||
.setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
|
||||
.setProperty("doubleKey1", 1.0, 2.0, 3.0)
|
||||
.setProperty("byteKey1", sByteArray1, sByteArray2)
|
||||
.setProperty("longKey1", 1L, 2L, 3L)
|
||||
.build();
|
||||
assertThat(document1).isEqualTo(document2);
|
||||
assertThat(document1.hashCode()).isEqualTo(document2.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDocumentEquals_Failure() {
|
||||
AppSearchDocument document1 = new AppSearchDocument.Builder("uri1", "schemaType1")
|
||||
.setCreationTimestampMillis(5L)
|
||||
.setProperty("longKey1", 1L, 2L, 3L)
|
||||
.build();
|
||||
|
||||
// Create second document with same order but different value.
|
||||
AppSearchDocument document2 = new AppSearchDocument.Builder("uri1", "schemaType1")
|
||||
.setCreationTimestampMillis(5L)
|
||||
.setProperty("longKey1", 1L, 2L, 4L) // Different
|
||||
.build();
|
||||
assertThat(document1).isNotEqualTo(document2);
|
||||
assertThat(document1.hashCode()).isNotEqualTo(document2.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDocumentEquals_Failure_RepeatedFieldOrder() {
|
||||
AppSearchDocument document1 = new AppSearchDocument.Builder("uri1", "schemaType1")
|
||||
.setCreationTimestampMillis(5L)
|
||||
.setProperty("booleanKey1", true, false, true)
|
||||
.build();
|
||||
|
||||
// Create second document with same order but different value.
|
||||
AppSearchDocument document2 = new AppSearchDocument.Builder("uri1", "schemaType1")
|
||||
.setCreationTimestampMillis(5L)
|
||||
.setProperty("booleanKey1", true, true, false) // Different
|
||||
.build();
|
||||
assertThat(document1).isNotEqualTo(document2);
|
||||
assertThat(document1.hashCode()).isNotEqualTo(document2.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDocumentGetSingleValue() {
|
||||
AppSearchDocument document = new AppSearchDocument.Builder("uri1", "schemaType1")
|
||||
.setCreationTimestampMillis(5L)
|
||||
.setScore(1)
|
||||
.setTtlMillis(1L)
|
||||
.setProperty("longKey1", 1L)
|
||||
.setProperty("doubleKey1", 1.0)
|
||||
.setProperty("booleanKey1", true)
|
||||
.setProperty("stringKey1", "test-value1")
|
||||
.setProperty("byteKey1", sByteArray1)
|
||||
.setProperty("documentKey1", sDocumentProperties1)
|
||||
.build();
|
||||
assertThat(document.getUri()).isEqualTo("uri1");
|
||||
assertThat(document.getTtlMillis()).isEqualTo(1L);
|
||||
assertThat(document.getSchemaType()).isEqualTo("schemaType1");
|
||||
assertThat(document.getCreationTimestampMillis()).isEqualTo(5);
|
||||
assertThat(document.getScore()).isEqualTo(1);
|
||||
assertThat(document.getPropertyLong("longKey1")).isEqualTo(1L);
|
||||
assertThat(document.getPropertyDouble("doubleKey1")).isEqualTo(1.0);
|
||||
assertThat(document.getPropertyBoolean("booleanKey1")).isTrue();
|
||||
assertThat(document.getPropertyString("stringKey1")).isEqualTo("test-value1");
|
||||
assertThat(document.getPropertyBytes("byteKey1"))
|
||||
.asList().containsExactly((byte) 1, (byte) 2, (byte) 3);
|
||||
assertThat(document.getPropertyDocument("documentKey1")).isEqualTo(sDocumentProperties1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDocumentGetArrayValues() {
|
||||
AppSearchDocument document = new AppSearchDocument.Builder("uri1", "schemaType1")
|
||||
.setCreationTimestampMillis(5L)
|
||||
.setProperty("longKey1", 1L, 2L, 3L)
|
||||
.setProperty("doubleKey1", 1.0, 2.0, 3.0)
|
||||
.setProperty("booleanKey1", true, false, true)
|
||||
.setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
|
||||
.setProperty("byteKey1", sByteArray1, sByteArray2)
|
||||
.setProperty("documentKey1", sDocumentProperties1, sDocumentProperties2)
|
||||
.build();
|
||||
|
||||
assertThat(document.getUri()).isEqualTo("uri1");
|
||||
assertThat(document.getSchemaType()).isEqualTo("schemaType1");
|
||||
assertThat(document.getPropertyLongArray("longKey1")).asList().containsExactly(1L, 2L, 3L);
|
||||
assertThat(document.getPropertyDoubleArray("doubleKey1")).usingExactEquality()
|
||||
.containsExactly(1.0, 2.0, 3.0);
|
||||
assertThat(document.getPropertyBooleanArray("booleanKey1")).asList()
|
||||
.containsExactly(true, false, true);
|
||||
assertThat(document.getPropertyStringArray("stringKey1")).asList()
|
||||
.containsExactly("test-value1", "test-value2", "test-value3");
|
||||
assertThat(document.getPropertyBytesArray("byteKey1")).asList()
|
||||
.containsExactly(sByteArray1, sByteArray2);
|
||||
assertThat(document.getPropertyDocumentArray("documentKey1")).asList()
|
||||
.containsExactly(sDocumentProperties1, sDocumentProperties2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDocumentGetValues_DifferentTypes() {
|
||||
AppSearchDocument document = new AppSearchDocument.Builder("uri1", "schemaType1")
|
||||
.setScore(1)
|
||||
.setProperty("longKey1", 1L)
|
||||
.setProperty("booleanKey1", true, false, true)
|
||||
.setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
|
||||
.build();
|
||||
|
||||
// Get a value for a key that doesn't exist
|
||||
assertThat(document.getPropertyDouble("doubleKey1")).isNull();
|
||||
assertThat(document.getPropertyDoubleArray("doubleKey1")).isNull();
|
||||
|
||||
// Get a value with a single element as an array and as a single value
|
||||
assertThat(document.getPropertyLong("longKey1")).isEqualTo(1L);
|
||||
assertThat(document.getPropertyLongArray("longKey1")).asList().containsExactly(1L);
|
||||
|
||||
// Get a value with multiple elements as an array and as a single value
|
||||
assertThat(document.getPropertyString("stringKey1")).isEqualTo("test-value1");
|
||||
assertThat(document.getPropertyStringArray("stringKey1")).asList()
|
||||
.containsExactly("test-value1", "test-value2", "test-value3");
|
||||
|
||||
// Get a value of the wrong type
|
||||
assertThat(document.getPropertyDouble("longKey1")).isNull();
|
||||
assertThat(document.getPropertyDoubleArray("longKey1")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDocumentInvalid() {
|
||||
AppSearchDocument.Builder builder = new AppSearchDocument.Builder("uri1", "schemaType1");
|
||||
assertThrows(
|
||||
IllegalArgumentException.class, () -> builder.setProperty("test", new boolean[]{}));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDocumentProtoPopulation() {
|
||||
AppSearchDocument document = new AppSearchDocument.Builder("uri1", "schemaType1")
|
||||
.setCreationTimestampMillis(5L)
|
||||
.setScore(1)
|
||||
.setTtlMillis(1L)
|
||||
.setProperty("longKey1", 1L)
|
||||
.setProperty("doubleKey1", 1.0)
|
||||
.setProperty("booleanKey1", true)
|
||||
.setProperty("stringKey1", "test-value1")
|
||||
.setProperty("byteKey1", sByteArray1)
|
||||
.setProperty("documentKey1", sDocumentProperties1)
|
||||
.build();
|
||||
|
||||
// Create the Document proto. Need to sort the property order by key.
|
||||
DocumentProto.Builder documentProtoBuilder = DocumentProto.newBuilder()
|
||||
.setUri("uri1")
|
||||
.setSchema("schemaType1")
|
||||
.setCreationTimestampMs(5L)
|
||||
.setScore(1)
|
||||
.setTtlMs(1L);
|
||||
HashMap<String, PropertyProto.Builder> propertyProtoMap = new HashMap<>();
|
||||
propertyProtoMap.put("longKey1",
|
||||
PropertyProto.newBuilder().setName("longKey1").addInt64Values(1L));
|
||||
propertyProtoMap.put("doubleKey1",
|
||||
PropertyProto.newBuilder().setName("doubleKey1").addDoubleValues(1.0));
|
||||
propertyProtoMap.put("booleanKey1",
|
||||
PropertyProto.newBuilder().setName("booleanKey1").addBooleanValues(true));
|
||||
propertyProtoMap.put("stringKey1",
|
||||
PropertyProto.newBuilder().setName("stringKey1").addStringValues("test-value1"));
|
||||
propertyProtoMap.put("byteKey1",
|
||||
PropertyProto.newBuilder().setName("byteKey1").addBytesValues(
|
||||
ByteString.copyFrom(sByteArray1)));
|
||||
propertyProtoMap.put("documentKey1",
|
||||
PropertyProto.newBuilder().setName("documentKey1")
|
||||
.addDocumentValues(sDocumentProperties1.getProto()));
|
||||
List<String> sortedKey = new ArrayList<>(propertyProtoMap.keySet());
|
||||
Collections.sort(sortedKey);
|
||||
for (String key : sortedKey) {
|
||||
documentProtoBuilder.addProperties(propertyProtoMap.get(key));
|
||||
}
|
||||
assertThat(document.getProto()).isEqualTo(documentProtoBuilder.build());
|
||||
}
|
||||
}
|
||||
@@ -1,53 +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 static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
@SmallTest
|
||||
public class AppSearchEmailTest {
|
||||
|
||||
@Test
|
||||
public void testBuildEmailAndGetValue() {
|
||||
AppSearchEmail email = new AppSearchEmail.Builder("uri")
|
||||
.setFrom("FakeFromAddress")
|
||||
.setCc("CC1", "CC2")
|
||||
// Score and Property are mixed into the middle to make sure DocumentBuilder's
|
||||
// methods can be interleaved with EmailBuilder's methods.
|
||||
.setScore(1)
|
||||
.setProperty("propertyKey", "propertyValue1", "propertyValue2")
|
||||
.setSubject("subject")
|
||||
.setBody("EmailBody")
|
||||
.build();
|
||||
|
||||
assertThat(email.getUri()).isEqualTo("uri");
|
||||
assertThat(email.getFrom()).isEqualTo("FakeFromAddress");
|
||||
assertThat(email.getTo()).isNull();
|
||||
assertThat(email.getCc()).asList().containsExactly("CC1", "CC2");
|
||||
assertThat(email.getBcc()).isNull();
|
||||
assertThat(email.getScore()).isEqualTo(1);
|
||||
assertThat(email.getPropertyString("propertyKey")).isEqualTo("propertyValue1");
|
||||
assertThat(email.getPropertyStringArray("propertyKey")).asList().containsExactly(
|
||||
"propertyValue1", "propertyValue2");
|
||||
assertThat(email.getSubject()).isEqualTo("subject");
|
||||
assertThat(email.getBody()).isEqualTo("EmailBody");
|
||||
}
|
||||
}
|
||||
@@ -1,166 +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 static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.testng.Assert.assertThrows;
|
||||
import static org.testng.Assert.expectThrows;
|
||||
|
||||
import android.app.appsearch.AppSearchSchema.PropertyConfig;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.google.android.icing.proto.IndexingConfig.TokenizerType;
|
||||
import com.google.android.icing.proto.PropertyConfigProto;
|
||||
import com.google.android.icing.proto.SchemaTypeConfigProto;
|
||||
import com.google.android.icing.proto.TermMatchType;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
@SmallTest
|
||||
public class AppSearchSchemaTest {
|
||||
@Test
|
||||
public void testGetProto_Email() {
|
||||
AppSearchSchema emailSchema = AppSearchSchema.newBuilder("Email")
|
||||
.addProperty(AppSearchSchema.newPropertyBuilder("subject")
|
||||
.setDataType(PropertyConfig.DATA_TYPE_STRING)
|
||||
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
|
||||
.setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
|
||||
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
|
||||
.build()
|
||||
).addProperty(AppSearchSchema.newPropertyBuilder("body")
|
||||
.setDataType(PropertyConfig.DATA_TYPE_STRING)
|
||||
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
|
||||
.setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
|
||||
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
|
||||
.build()
|
||||
).build();
|
||||
|
||||
SchemaTypeConfigProto expectedEmailProto = SchemaTypeConfigProto.newBuilder()
|
||||
.setSchemaType("Email")
|
||||
.addProperties(PropertyConfigProto.newBuilder()
|
||||
.setPropertyName("subject")
|
||||
.setDataType(PropertyConfigProto.DataType.Code.STRING)
|
||||
.setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
|
||||
.setIndexingConfig(
|
||||
com.google.android.icing.proto.IndexingConfig.newBuilder()
|
||||
.setTokenizerType(TokenizerType.Code.PLAIN)
|
||||
.setTermMatchType(TermMatchType.Code.PREFIX)
|
||||
)
|
||||
).addProperties(PropertyConfigProto.newBuilder()
|
||||
.setPropertyName("body")
|
||||
.setDataType(PropertyConfigProto.DataType.Code.STRING)
|
||||
.setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
|
||||
.setIndexingConfig(
|
||||
com.google.android.icing.proto.IndexingConfig.newBuilder()
|
||||
.setTokenizerType(TokenizerType.Code.PLAIN)
|
||||
.setTermMatchType(TermMatchType.Code.PREFIX)
|
||||
)
|
||||
).build();
|
||||
|
||||
assertThat(emailSchema.getProto()).isEqualTo(expectedEmailProto);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetProto_MusicRecording() {
|
||||
AppSearchSchema musicRecordingSchema = AppSearchSchema.newBuilder("MusicRecording")
|
||||
.addProperty(AppSearchSchema.newPropertyBuilder("artist")
|
||||
.setDataType(PropertyConfig.DATA_TYPE_STRING)
|
||||
.setCardinality(PropertyConfig.CARDINALITY_REPEATED)
|
||||
.setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
|
||||
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
|
||||
.build()
|
||||
).addProperty(AppSearchSchema.newPropertyBuilder("pubDate")
|
||||
.setDataType(PropertyConfig.DATA_TYPE_INT64)
|
||||
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
|
||||
.setIndexingType(PropertyConfig.INDEXING_TYPE_NONE)
|
||||
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_NONE)
|
||||
.build()
|
||||
).build();
|
||||
|
||||
SchemaTypeConfigProto expectedMusicRecordingProto = SchemaTypeConfigProto.newBuilder()
|
||||
.setSchemaType("MusicRecording")
|
||||
.addProperties(PropertyConfigProto.newBuilder()
|
||||
.setPropertyName("artist")
|
||||
.setDataType(PropertyConfigProto.DataType.Code.STRING)
|
||||
.setCardinality(PropertyConfigProto.Cardinality.Code.REPEATED)
|
||||
.setIndexingConfig(
|
||||
com.google.android.icing.proto.IndexingConfig.newBuilder()
|
||||
.setTokenizerType(TokenizerType.Code.PLAIN)
|
||||
.setTermMatchType(TermMatchType.Code.PREFIX)
|
||||
)
|
||||
).addProperties(PropertyConfigProto.newBuilder()
|
||||
.setPropertyName("pubDate")
|
||||
.setDataType(PropertyConfigProto.DataType.Code.INT64)
|
||||
.setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
|
||||
.setIndexingConfig(
|
||||
com.google.android.icing.proto.IndexingConfig.newBuilder()
|
||||
.setTokenizerType(TokenizerType.Code.NONE)
|
||||
.setTermMatchType(TermMatchType.Code.UNKNOWN)
|
||||
)
|
||||
).build();
|
||||
|
||||
assertThat(musicRecordingSchema.getProto()).isEqualTo(expectedMusicRecordingProto);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidEnums() {
|
||||
PropertyConfig.Builder builder = AppSearchSchema.newPropertyBuilder("test");
|
||||
assertThrows(IllegalArgumentException.class, () -> builder.setDataType(99));
|
||||
assertThrows(IllegalArgumentException.class, () -> builder.setCardinality(99));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMissingFields() {
|
||||
PropertyConfig.Builder builder = AppSearchSchema.newPropertyBuilder("test");
|
||||
Exception e = expectThrows(IllegalSchemaException.class, builder::build);
|
||||
assertThat(e).hasMessageThat().contains("Missing field: dataType");
|
||||
|
||||
builder.setDataType(PropertyConfig.DATA_TYPE_DOCUMENT);
|
||||
e = expectThrows(IllegalSchemaException.class, builder::build);
|
||||
assertThat(e).hasMessageThat().contains("Missing field: schemaType");
|
||||
|
||||
builder.setSchemaType("TestType");
|
||||
e = expectThrows(IllegalSchemaException.class, builder::build);
|
||||
assertThat(e).hasMessageThat().contains("Missing field: cardinality");
|
||||
|
||||
builder.setCardinality(PropertyConfig.CARDINALITY_REPEATED);
|
||||
builder.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDuplicateProperties() {
|
||||
AppSearchSchema.Builder builder = AppSearchSchema.newBuilder("Email")
|
||||
.addProperty(AppSearchSchema.newPropertyBuilder("subject")
|
||||
.setDataType(PropertyConfig.DATA_TYPE_STRING)
|
||||
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
|
||||
.setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
|
||||
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
|
||||
.build()
|
||||
).addProperty(AppSearchSchema.newPropertyBuilder("subject")
|
||||
.setDataType(PropertyConfig.DATA_TYPE_STRING)
|
||||
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
|
||||
.setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
|
||||
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
|
||||
.build()
|
||||
);
|
||||
|
||||
Exception e = expectThrows(IllegalSchemaException.class, builder::build);
|
||||
assertThat(e).hasMessageThat().contains("Property defined more than once: subject");
|
||||
}
|
||||
}
|
||||
@@ -1,66 +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 static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.testng.Assert.assertThrows;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.google.android.icing.proto.DocumentProto;
|
||||
import com.google.android.icing.proto.SearchResultProto;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
@SmallTest
|
||||
public class SearchResultsTest {
|
||||
|
||||
@Test
|
||||
public void testSearchResultsEqual() {
|
||||
final String uri = "testUri";
|
||||
final String schemaType = "testSchema";
|
||||
SearchResultProto.ResultProto result1 = SearchResultProto.ResultProto.newBuilder()
|
||||
.setDocument(DocumentProto.newBuilder()
|
||||
.setUri(uri)
|
||||
.setSchema(schemaType)
|
||||
.build())
|
||||
.build();
|
||||
SearchResultProto searchResults1 = SearchResultProto.newBuilder()
|
||||
.addResults(result1)
|
||||
.build();
|
||||
SearchResults res1 = new SearchResults(searchResults1);
|
||||
SearchResultProto.ResultProto result2 = SearchResultProto.ResultProto.newBuilder()
|
||||
.setDocument(DocumentProto.newBuilder()
|
||||
.setUri(uri)
|
||||
.setSchema(schemaType)
|
||||
.build())
|
||||
.build();
|
||||
SearchResultProto searchResults2 = SearchResultProto.newBuilder()
|
||||
.addResults(result2)
|
||||
.build();
|
||||
SearchResults res2 = new SearchResults(searchResults2);
|
||||
assertThat(res1.toString()).isEqualTo(res2.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildSearchSpecWithoutTermMatchType() {
|
||||
assertThrows(RuntimeException.class, () -> SearchSpec.newBuilder()
|
||||
.setSchemaTypes("testSchemaType")
|
||||
.build());
|
||||
}
|
||||
}
|
||||
@@ -1,200 +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 static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.google.android.icing.proto.DocumentProto;
|
||||
import com.google.android.icing.proto.PropertyProto;
|
||||
import com.google.android.icing.proto.SearchResultProto;
|
||||
import com.google.android.icing.proto.SnippetMatchProto;
|
||||
import com.google.android.icing.proto.SnippetProto;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
@SmallTest
|
||||
public class SnippetTest {
|
||||
|
||||
// TODO(sidchhabra): Add tests for Double and Long Snippets.
|
||||
@Test
|
||||
public void testSingleStringSnippet() {
|
||||
|
||||
final String propertyKeyString = "content";
|
||||
final String propertyValueString = "A commonly used fake word is foo.\n"
|
||||
+ " Another nonsense word that’s used a lot\n"
|
||||
+ " is bar.\n";
|
||||
final String uri = "uri1";
|
||||
final String schemaType = "schema1";
|
||||
final String searchWord = "foo";
|
||||
final String exactMatch = "foo";
|
||||
final String window = "is foo";
|
||||
|
||||
// Building the SearchResult received from query.
|
||||
PropertyProto property = PropertyProto.newBuilder()
|
||||
.setName(propertyKeyString)
|
||||
.addStringValues(propertyValueString)
|
||||
.build();
|
||||
DocumentProto documentProto = DocumentProto.newBuilder()
|
||||
.setUri(uri)
|
||||
.setSchema(schemaType)
|
||||
.addProperties(property)
|
||||
.build();
|
||||
SnippetProto snippetProto = SnippetProto.newBuilder()
|
||||
.addEntries(SnippetProto.EntryProto.newBuilder()
|
||||
.setPropertyName(propertyKeyString)
|
||||
.addSnippetMatches(SnippetMatchProto.newBuilder()
|
||||
.setValuesIndex(0)
|
||||
.setExactMatchPosition(29)
|
||||
.setExactMatchBytes(3)
|
||||
.setWindowPosition(26)
|
||||
.setWindowBytes(6)
|
||||
.build())
|
||||
.build())
|
||||
.build();
|
||||
SearchResultProto.ResultProto resultProto = SearchResultProto.ResultProto.newBuilder()
|
||||
.setDocument(documentProto)
|
||||
.setSnippet(snippetProto)
|
||||
.build();
|
||||
SearchResultProto searchResultProto = SearchResultProto.newBuilder()
|
||||
.addResults(resultProto)
|
||||
.build();
|
||||
SearchResults searchResults = new SearchResults(searchResultProto);
|
||||
|
||||
// Making ResultReader and getting Snippet values.
|
||||
while (searchResults.hasNext()) {
|
||||
SearchResults.Result result = searchResults.next();
|
||||
MatchInfo match = result.getMatchInfo().get(0);
|
||||
assertThat(match.getPropertyPath()).isEqualTo(propertyKeyString);
|
||||
assertThat(match.getFullText()).isEqualTo(propertyValueString);
|
||||
assertThat(match.getExactMatch()).isEqualTo(exactMatch);
|
||||
assertThat(match.getSnippet()).isEqualTo(window);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(sidchhabra): Add tests for Double and Long Snippets.
|
||||
@Test
|
||||
public void testNoSnippets() {
|
||||
|
||||
final String propertyKeyString = "content";
|
||||
final String propertyValueString = "A commonly used fake word is foo.\n"
|
||||
+ " Another nonsense word that’s used a lot\n"
|
||||
+ " is bar.\n";
|
||||
final String uri = "uri1";
|
||||
final String schemaType = "schema1";
|
||||
final String searchWord = "foo";
|
||||
final String exactMatch = "foo";
|
||||
final String window = "is foo";
|
||||
|
||||
// Building the SearchResult received from query.
|
||||
PropertyProto property = PropertyProto.newBuilder()
|
||||
.setName(propertyKeyString)
|
||||
.addStringValues(propertyValueString)
|
||||
.build();
|
||||
DocumentProto documentProto = DocumentProto.newBuilder()
|
||||
.setUri(uri)
|
||||
.setSchema(schemaType)
|
||||
.addProperties(property)
|
||||
.build();
|
||||
SearchResultProto.ResultProto resultProto = SearchResultProto.ResultProto.newBuilder()
|
||||
.setDocument(documentProto)
|
||||
.build();
|
||||
SearchResultProto searchResultProto = SearchResultProto.newBuilder()
|
||||
.addResults(resultProto)
|
||||
.build();
|
||||
SearchResults searchResults = new SearchResults(searchResultProto);
|
||||
|
||||
while (searchResults.hasNext()) {
|
||||
SearchResults.Result result = searchResults.next();
|
||||
assertThat(result.getMatchInfo()).isEqualTo(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleStringSnippet() {
|
||||
final String searchWord = "Test";
|
||||
|
||||
// Building the SearchResult received from query.
|
||||
PropertyProto property1 = PropertyProto.newBuilder()
|
||||
.setName("sender.name")
|
||||
.addStringValues("Test Name Jr.")
|
||||
.build();
|
||||
PropertyProto property2 = PropertyProto.newBuilder()
|
||||
.setName("sender.email")
|
||||
.addStringValues("TestNameJr@gmail.com")
|
||||
.build();
|
||||
DocumentProto documentProto = DocumentProto.newBuilder()
|
||||
.setUri("uri1")
|
||||
.setSchema("schema1")
|
||||
.addProperties(property1)
|
||||
.addProperties(property2)
|
||||
.build();
|
||||
SnippetProto snippetProto = SnippetProto.newBuilder()
|
||||
.addEntries(
|
||||
SnippetProto.EntryProto.newBuilder()
|
||||
.setPropertyName("sender.name")
|
||||
.addSnippetMatches(
|
||||
SnippetMatchProto.newBuilder()
|
||||
.setValuesIndex(0)
|
||||
.setExactMatchPosition(0)
|
||||
.setExactMatchBytes(4)
|
||||
.setWindowPosition(0)
|
||||
.setWindowBytes(9)
|
||||
.build())
|
||||
.build())
|
||||
.addEntries(
|
||||
SnippetProto.EntryProto.newBuilder()
|
||||
.setPropertyName("sender.email")
|
||||
.addSnippetMatches(
|
||||
SnippetMatchProto.newBuilder()
|
||||
.setValuesIndex(0)
|
||||
.setExactMatchPosition(0)
|
||||
.setExactMatchBytes(20)
|
||||
.setWindowPosition(0)
|
||||
.setWindowBytes(20)
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
.build();
|
||||
SearchResultProto.ResultProto resultProto = SearchResultProto.ResultProto.newBuilder()
|
||||
.setDocument(documentProto)
|
||||
.setSnippet(snippetProto)
|
||||
.build();
|
||||
SearchResultProto searchResultProto = SearchResultProto.newBuilder()
|
||||
.addResults(resultProto)
|
||||
.build();
|
||||
SearchResults searchResults = new SearchResults(searchResultProto);
|
||||
|
||||
// Making ResultReader and getting Snippet values.
|
||||
while (searchResults.hasNext()) {
|
||||
SearchResults.Result result = searchResults.next();
|
||||
|
||||
MatchInfo match1 = result.getMatchInfo().get(0);
|
||||
assertThat(match1.getPropertyPath()).isEqualTo("sender.name");
|
||||
assertThat(match1.getFullText()).isEqualTo("Test Name Jr.");
|
||||
assertThat(match1.getExactMatch()).isEqualTo("Test");
|
||||
assertThat(match1.getSnippet()).isEqualTo("Test Name");
|
||||
|
||||
MatchInfo match2 = result.getMatchInfo().get(1);
|
||||
assertThat(match2.getPropertyPath()).isEqualTo("sender.email");
|
||||
assertThat(match2.getFullText()).isEqualTo("TestNameJr@gmail.com");
|
||||
assertThat(match2.getExactMatch()).isEqualTo("TestNameJr@gmail.com");
|
||||
assertThat(match2.getSnippet()).isEqualTo("TestNameJr@gmail.com");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,98 +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.impl;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.app.appsearch.AppSearchDocument;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests that {@link AppSearchDocument} and {@link AppSearchDocument.Builder} are extendable by
|
||||
* developers.
|
||||
*
|
||||
* <p>This class is intentionally in a different package than {@link AppSearchDocument} to make sure
|
||||
* there are no package-private methods required for external developers to add custom types.
|
||||
*/
|
||||
@SmallTest
|
||||
public class CustomerDocumentTest {
|
||||
|
||||
private static byte[] sByteArray1 = new byte[]{(byte) 1, (byte) 2, (byte) 3};
|
||||
private static byte[] sByteArray2 = new byte[]{(byte) 4, (byte) 5, (byte) 6};
|
||||
private static AppSearchDocument sDocumentProperties1 = new AppSearchDocument
|
||||
.Builder("sDocumentProperties1", "sDocumentPropertiesSchemaType1")
|
||||
.build();
|
||||
private static AppSearchDocument sDocumentProperties2 = new AppSearchDocument
|
||||
.Builder("sDocumentProperties2", "sDocumentPropertiesSchemaType2")
|
||||
.build();
|
||||
|
||||
@Test
|
||||
public void testBuildCustomerDocument() {
|
||||
CustomerDocument customerDocument = new CustomerDocument.Builder("uri1")
|
||||
.setScore(1)
|
||||
.setCreationTimestampMillis(0)
|
||||
.setProperty("longKey1", 1L, 2L, 3L)
|
||||
.setProperty("doubleKey1", 1.0, 2.0, 3.0)
|
||||
.setProperty("booleanKey1", true, false, true)
|
||||
.setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
|
||||
.setProperty("byteKey1", sByteArray1, sByteArray2)
|
||||
.setProperty("documentKey1", sDocumentProperties1, sDocumentProperties2)
|
||||
.build();
|
||||
|
||||
assertThat(customerDocument.getUri()).isEqualTo("uri1");
|
||||
assertThat(customerDocument.getSchemaType()).isEqualTo("customerDocument");
|
||||
assertThat(customerDocument.getScore()).isEqualTo(1);
|
||||
assertThat(customerDocument.getCreationTimestampMillis()).isEqualTo(0L);
|
||||
assertThat(customerDocument.getPropertyLongArray("longKey1")).asList()
|
||||
.containsExactly(1L, 2L, 3L);
|
||||
assertThat(customerDocument.getPropertyDoubleArray("doubleKey1")).usingExactEquality()
|
||||
.containsExactly(1.0, 2.0, 3.0);
|
||||
assertThat(customerDocument.getPropertyBooleanArray("booleanKey1")).asList()
|
||||
.containsExactly(true, false, true);
|
||||
assertThat(customerDocument.getPropertyStringArray("stringKey1")).asList()
|
||||
.containsExactly("test-value1", "test-value2", "test-value3");
|
||||
assertThat(customerDocument.getPropertyBytesArray("byteKey1")).asList()
|
||||
.containsExactly(sByteArray1, sByteArray2);
|
||||
assertThat(customerDocument.getPropertyDocumentArray("documentKey1")).asList()
|
||||
.containsExactly(sDocumentProperties1, sDocumentProperties2);
|
||||
}
|
||||
|
||||
/**
|
||||
* An example document type for test purposes, defined outside of
|
||||
* {@link android.app.appsearch.AppSearch} (the way an external developer would define it).
|
||||
*/
|
||||
private static class CustomerDocument extends AppSearchDocument {
|
||||
private CustomerDocument(AppSearchDocument document) {
|
||||
super(document);
|
||||
}
|
||||
|
||||
public static class Builder extends AppSearchDocument.Builder<CustomerDocument.Builder> {
|
||||
private Builder(@NonNull String uri) {
|
||||
super(uri, "customerDocument");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomerDocument build() {
|
||||
return new CustomerDocument(super.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -295,8 +295,6 @@ public final class SystemServer {
|
||||
"com.android.server.DeviceIdleController";
|
||||
private static final String BLOB_STORE_MANAGER_SERVICE_CLASS =
|
||||
"com.android.server.blob.BlobStoreManagerService";
|
||||
private static final String APP_SEARCH_MANAGER_SERVICE_CLASS =
|
||||
"com.android.server.appsearch.AppSearchManagerService";
|
||||
|
||||
private static final String TETHERING_CONNECTOR_CLASS = "android.net.ITetheringConnector";
|
||||
|
||||
@@ -2168,10 +2166,6 @@ public final class SystemServer {
|
||||
mSystemServiceManager.startBootPhase(t, SystemService.PHASE_DEVICE_SPECIFIC_SERVICES_READY);
|
||||
t.traceEnd();
|
||||
|
||||
t.traceBegin("AppSearchManagerService");
|
||||
mSystemServiceManager.startService(APP_SEARCH_MANAGER_SERVICE_CLASS);
|
||||
t.traceEnd();
|
||||
|
||||
ConcurrentUtils.waitForFutureNoInterrupt(mBlobStoreServiceStart,
|
||||
START_BLOB_STORE_SERVICE);
|
||||
|
||||
|
||||
@@ -43,7 +43,6 @@ android_test {
|
||||
"platformprotosnano",
|
||||
"hamcrest-library",
|
||||
"servicestests-utils",
|
||||
"service-appsearch",
|
||||
"service-jobscheduler",
|
||||
"service-permission",
|
||||
// TODO: remove once Android migrates to JUnit 4.12,
|
||||
|
||||
@@ -1,109 +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.impl;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.testng.Assert.expectThrows;
|
||||
|
||||
import android.annotation.UserIdInt;
|
||||
import android.content.Context;
|
||||
import android.os.UserHandle;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.google.android.icing.proto.IndexingConfig;
|
||||
import com.google.android.icing.proto.PropertyConfigProto;
|
||||
import com.google.android.icing.proto.SchemaProto;
|
||||
import com.google.android.icing.proto.SchemaTypeConfigProto;
|
||||
import com.google.android.icing.proto.TermMatchType;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class AppSearchImplTest {
|
||||
private final Context mContext = InstrumentationRegistry.getContext();
|
||||
private final @UserIdInt int mUserId = UserHandle.getCallingUserId();
|
||||
|
||||
@Test
|
||||
public void testRewriteSchemaTypes() {
|
||||
SchemaProto inSchema = SchemaProto.newBuilder()
|
||||
.addTypes(SchemaTypeConfigProto.newBuilder()
|
||||
.setSchemaType("TestType")
|
||||
.addProperties(PropertyConfigProto.newBuilder()
|
||||
.setPropertyName("subject")
|
||||
.setDataType(PropertyConfigProto.DataType.Code.STRING)
|
||||
.setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
|
||||
.setIndexingConfig(
|
||||
IndexingConfig.newBuilder()
|
||||
.setTokenizerType(
|
||||
IndexingConfig.TokenizerType.Code.PLAIN)
|
||||
.setTermMatchType(TermMatchType.Code.PREFIX)
|
||||
.build()
|
||||
).build()
|
||||
).addProperties(PropertyConfigProto.newBuilder()
|
||||
.setPropertyName("link")
|
||||
.setDataType(PropertyConfigProto.DataType.Code.DOCUMENT)
|
||||
.setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
|
||||
.setSchemaType("RefType")
|
||||
.build()
|
||||
).build()
|
||||
).build();
|
||||
|
||||
SchemaProto expectedSchema = SchemaProto.newBuilder()
|
||||
.addTypes(SchemaTypeConfigProto.newBuilder()
|
||||
.setSchemaType("com.android.server.appsearch.impl@42:TestType")
|
||||
.addProperties(PropertyConfigProto.newBuilder()
|
||||
.setPropertyName("subject")
|
||||
.setDataType(PropertyConfigProto.DataType.Code.STRING)
|
||||
.setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
|
||||
.setIndexingConfig(
|
||||
IndexingConfig.newBuilder()
|
||||
.setTokenizerType(
|
||||
IndexingConfig.TokenizerType.Code.PLAIN)
|
||||
.setTermMatchType(TermMatchType.Code.PREFIX)
|
||||
.build()
|
||||
).build()
|
||||
).addProperties(PropertyConfigProto.newBuilder()
|
||||
.setPropertyName("link")
|
||||
.setDataType(PropertyConfigProto.DataType.Code.DOCUMENT)
|
||||
.setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
|
||||
.setSchemaType("com.android.server.appsearch.impl@42:RefType")
|
||||
.build()
|
||||
).build()
|
||||
).build();
|
||||
|
||||
AppSearchImpl impl = new AppSearchImpl(mContext, mUserId);
|
||||
SchemaProto.Builder actualSchema = inSchema.toBuilder();
|
||||
impl.rewriteSchemaTypes("com.android.server.appsearch.impl@42:", actualSchema);
|
||||
|
||||
assertThat(actualSchema.build()).isEqualTo(expectedSchema);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPackageNotFound() {
|
||||
AppSearchImpl impl = new AppSearchImpl(mContext, mUserId);
|
||||
IllegalStateException e = expectThrows(
|
||||
IllegalStateException.class,
|
||||
() -> impl.setSchema(
|
||||
/*callingUid=*/Integer.MAX_VALUE,
|
||||
SchemaProto.getDefaultInstance(),
|
||||
/*forceOverride=*/false));
|
||||
assertThat(e).hasMessageThat().contains("Failed to look up package name");
|
||||
}
|
||||
}
|
||||
@@ -1,127 +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.impl;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.google.android.icing.proto.DocumentProto;
|
||||
import com.google.android.icing.proto.PropertyProto;
|
||||
import com.google.android.icing.proto.SearchResultProto;
|
||||
import com.google.android.icing.proto.StatusProto;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class FakeIcingTest {
|
||||
@Test
|
||||
public void query() {
|
||||
FakeIcing icing = new FakeIcing();
|
||||
icing.put(createDoc("uri:cat", "The cat said meow"));
|
||||
icing.put(createDoc("uri:dog", "The dog said woof"));
|
||||
|
||||
assertThat(queryGetUris(icing, "meow")).containsExactly("uri:cat");
|
||||
assertThat(queryGetUris(icing, "said")).containsExactly("uri:cat", "uri:dog");
|
||||
assertThat(queryGetUris(icing, "fred")).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryNorm() {
|
||||
FakeIcing icing = new FakeIcing();
|
||||
icing.put(createDoc("uri:cat", "The cat said meow"));
|
||||
icing.put(createDoc("uri:dog", "The dog said woof"));
|
||||
|
||||
assertThat(queryGetUris(icing, "the")).containsExactly("uri:cat", "uri:dog");
|
||||
assertThat(queryGetUris(icing, "The")).containsExactly("uri:cat", "uri:dog");
|
||||
assertThat(queryGetUris(icing, "tHe")).containsExactly("uri:cat", "uri:dog");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void get() {
|
||||
DocumentProto cat = createDoc("uri:cat", "The cat said meow");
|
||||
FakeIcing icing = new FakeIcing();
|
||||
icing.put(cat);
|
||||
assertThat(icing.get("uri:cat")).isEqualTo(cat);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replace() {
|
||||
DocumentProto cat = createDoc("uri:cat", "The cat said meow");
|
||||
DocumentProto dog = createDoc("uri:dog", "The dog said woof");
|
||||
|
||||
FakeIcing icing = new FakeIcing();
|
||||
icing.put(cat);
|
||||
icing.put(dog);
|
||||
|
||||
assertThat(queryGetUris(icing, "meow")).containsExactly("uri:cat");
|
||||
assertThat(queryGetUris(icing, "said")).containsExactly("uri:cat", "uri:dog");
|
||||
assertThat(icing.get("uri:cat")).isEqualTo(cat);
|
||||
|
||||
// Replace
|
||||
DocumentProto cat2 = createDoc("uri:cat", "The cat said purr");
|
||||
DocumentProto bird = createDoc("uri:bird", "The cat said tweet");
|
||||
icing.put(cat2);
|
||||
icing.put(bird);
|
||||
|
||||
assertThat(queryGetUris(icing, "meow")).isEmpty();
|
||||
assertThat(queryGetUris(icing, "said")).containsExactly("uri:cat", "uri:dog", "uri:bird");
|
||||
assertThat(icing.get("uri:cat")).isEqualTo(cat2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void delete() {
|
||||
DocumentProto cat = createDoc("uri:cat", "The cat said meow");
|
||||
DocumentProto dog = createDoc("uri:dog", "The dog said woof");
|
||||
|
||||
FakeIcing icing = new FakeIcing();
|
||||
icing.put(cat);
|
||||
icing.put(dog);
|
||||
|
||||
assertThat(queryGetUris(icing, "meow")).containsExactly("uri:cat");
|
||||
assertThat(queryGetUris(icing, "said")).containsExactly("uri:cat", "uri:dog");
|
||||
assertThat(icing.get("uri:cat")).isEqualTo(cat);
|
||||
|
||||
// Delete
|
||||
icing.delete("uri:cat");
|
||||
icing.delete("uri:notreal");
|
||||
|
||||
assertThat(queryGetUris(icing, "meow")).isEmpty();
|
||||
assertThat(queryGetUris(icing, "said")).containsExactly("uri:dog");
|
||||
assertThat(icing.get("uri:cat")).isNull();
|
||||
}
|
||||
|
||||
private static DocumentProto createDoc(String uri, String body) {
|
||||
return DocumentProto.newBuilder()
|
||||
.setUri(uri)
|
||||
.addProperties(PropertyProto.newBuilder().addStringValues(body))
|
||||
.build();
|
||||
}
|
||||
|
||||
private static List<String> queryGetUris(FakeIcing icing, String term) {
|
||||
List<String> uris = new ArrayList<>();
|
||||
SearchResultProto results = icing.query(term);
|
||||
assertThat(results.getStatus().getCode()).isEqualTo(StatusProto.Code.OK);
|
||||
for (SearchResultProto.ResultProto result : results.getResultsList()) {
|
||||
uris.add(result.getDocument().getUri());
|
||||
}
|
||||
return uris;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user