DO NOT MERGE: Remove AppSearch from Android R.

Test: presubmit
Bug: 150249538

Change-Id: Idb86be6586ae400552f8ceeca5c667fba8e0c43a
This commit is contained in:
Alexander Dorokhine
2020-02-25 21:15:03 -08:00
parent bd463a2b2d
commit 0b291791c8
42 changed files with 0 additions and 4434 deletions

View File

@@ -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",
],

View File

@@ -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",
}

View File

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

View File

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

View File

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

View File

@@ -1,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,
}

View File

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

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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());
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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)));
}
}

View File

@@ -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());
}
}
}
}

View File

@@ -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}&lt;{@link Void}&gt;. 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}&lt;byte[]&gt; of serialized DocumentProtos.
* @param callback
* {@link AndroidFuture}&lt;{@link AppSearchBatchResult}&lt;{@link String}, {@link Void}&gt;&gt;.
* If the call fails to start, {@code callback} will be completed exceptionally. Otherwise,
* {@code callback} will be completed with an
* {@link AppSearchBatchResult}&lt;{@link String}, {@link Void}&gt;
* 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);
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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 thats 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 thats 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
* thats 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;
}
}

View File

@@ -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();
}
}

View File

@@ -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());
}
}
}

View File

@@ -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"],
}

View File

@@ -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());
}
}
}

View File

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

View File

@@ -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 + "/";
}
}

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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}.

View File

@@ -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",

View File

@@ -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",
],

View File

@@ -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());
}
}

View File

@@ -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");
}
}

View File

@@ -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");
}
}

View File

@@ -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());
}
}

View File

@@ -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 thats 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 thats 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");
}
}
}

View File

@@ -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());
}
}
}
}

View File

@@ -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);

View File

@@ -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,

View File

@@ -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");
}
}

View File

@@ -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;
}
}